Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> Definition def = reachingDef.getDef(name, cfgNode); // TODO(nicksantos): We need to add some notion of @const outer // scope vars. We can inline those just fine. if (def != null && !reachingDef.dependsOnOuterScopeVars(def)) { candidates.add(new Candidate(name, def, n, cfgNode)); } } } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Models the connection between a definition and a use of that definition. */ private class Candidate { // Name of the variable. private final String varName; // Nodes related to the definition. private Node def; private final Definition defMetadata; // Nodes related to the use. private final Node use; private final Node useCfgNode; // Number of uses of the variable within the current CFG node. private int numUsesWithinCfgNode; Candidate(String varName, Definition defMetadata, Node use, Node useCfgNode) { Preconditions.checkArgument(use.isName()); this.varName = varName; this.defMetadata = defMetadata; this.use = use; this.useCfgNode = useCfgNode; } private Node getDefCfgNode() { return defMetadata.node; } private boolean canInline(final Scope scope) { // Cannot inline a parameter. if (getDefCfgNode().isFunction()) { return false; } // If one of our dependencies has been inlined, then our dependency // graph is wrong. Re-computing it would take another CFG computation, // so we just back off for now. for (Var dependency : defMetadata.depends) { if (inlinedNewDependencies.contains(dependency)) { return false; } } getDefinition(getDefCfgNode()); getNumUseInUseCfgNode(useCfgNode); // Definition was not found. if (def == null) { return false; } // Check that the assignment isn't used as a R-Value. // TODO(user): Certain cases we can still inline. if (def.isAssign() && !NodeUtil.isExprAssign(def.getParent())) { return false; } // The right of the definition has side effect: // Example, for x: // x = readProp(b), modifyProp(b); print(x); if (checkRightOf(def, getDefCfgNode(), SIDE_EFFECT_PREDICATE)) { return false; } // Similar check as the above but this time, all the sub-expressions // left of the use of the variable. // x = readProp(b); modifyProp(b), print(x); if (checkLeftOf(use, useCfgNode, SIDE_EFFECT_PREDICATE)) { return false; } // TODO(user): Side-effect is OK sometimes. As long

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> as there are no // side-effect function down all paths to the use. Once we have all the // side-effect analysis tool. if (NodeUtil.mayHaveSideEffects(def.getLastChild(), compiler)) { return false; } // TODO(user): We could inline all the uses if the expression is short. // Finally we have to make sure that there are no more than one use // in the program and in the CFG node. Even when it is semantically // correctly inlining twice increases code size. if (numUsesWithinCfgNode != 1) { return false; } // Make sure that the name is not within a loop if (NodeUtil.isWithinLoop(use)) { return false; } Collection<Node> uses = reachingUses.getUses(varName, getDefCfgNode()); if (uses.size() != 1) { return false; } // We give up inlining stuff with R-Value that has: // 1) GETPROP, GETELEM, // 2) anything that creates a new object. // 3) a direct reference to a catch expression. // Example: // var x = a.b.c; j.c = 1; print(x); // Inlining print(a.b.c) is not safe consider j and be alias to a.b. // TODO(user): We could get more accuracy by looking more in-detail // what j is and what x is trying to into to. // TODO(johnlenz): rework catch expression handling when we // have lexical scope support so catch expressions don't // need to be special cased. if (NodeUtil.has(def.getLastChild(), new Predicate<Node>() { @Override public boolean apply(Node input) { switch (input.getType()) { case Token.GETELEM: case Token.GETPROP: case Token.ARRAYLIT: case Token.OBJECTLIT: case Token.REGEXP: case Token.NEW: return true; case Token.NAME: Var var = scope.getOwnSlot(input.getString()); if (var != null && var.getParentNode().isCatch()) { return true; } } return false; } }, new Predicate<Node>() { @Override public boolean apply(Node input) { // Recurse if the node is not a function. return !input.isFunction(); } })) { return false; } // We can skip the side effect check along the paths of two nodes if // they are just next to each other. if (NodeUtil.isStatementBlock(getDefCfgNode().getParent()) && getDefCfgNode().getNext() != useCfgNode) { // Similar side effect check as above but this time the side effect is // else where along the path. // x = readProp(b); while(modifyProp(b)) {}; print(x); CheckPaths

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>BetweenNodes<Node, ControlFlowGraph.Branch> pathCheck = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>( cfg, cfg.getDirectedGraphNode(getDefCfgNode()), cfg.getDirectedGraphNode(useCfgNode), SIDE_EFFECT_PREDICATE, Predicates. <DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(), false); if (pathCheck.somePathsSatisfyPredicate()) { return false; } } return true; } /** * Actual transformation. */ private void inlineVariable() { Node defParent = def.getParent(); Node useParent = use.getParent(); if (def.isAssign()) { Node rhs = def.getLastChild(); rhs.detachFromParent(); // Oh yes! I have grandparent to remove this. Preconditions.checkState(defParent.isExprResult()); while (defParent.getParent().isLabel()) { defParent = defParent.getParent(); } defParent.detachFromParent(); useParent.replaceChild(use, rhs); } else if (defParent.isVar()) { Node rhs = def.getLastChild(); def.removeChild(rhs); useParent.replaceChild(use, rhs); } else { Preconditions.checkState(false, "No other definitions can be inlined."); } compiler.reportCodeChange(); } /** * Set the def node * * @param n A node that has a corresponding CFG node in the CFG. */ private void getDefinition(Node n) { AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: if (n.getString().equals(varName) && n.hasChildren()) { def = n; } return; case Token.ASSIGN: Node lhs = n.getFirstChild(); if (lhs.isName() && lhs.getString().equals(varName)) { */ private static boolean checkRightOf( Node n, Node expressionRoot, Predicate<Node> predicate) { for (Node p = n; p != expressionRoot; p = p.getParent()) { for (Node cur = p.getNext(); cur != null; cur = cur.getNext()) { if (predicate.apply(cur)) { return true; } } } return false; } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the left of n. * * Example: * * Checked(), Checked(), n, NotChecked(), NotChecked(); */ private static boolean checkLeftOf( Node n, Node expressionRoot, Predicate<Node> predicate) { for (Node p = n; p != expressionRoot; p = p.getParent()) { for (Node

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> cur = p.getParent().getFirstChild(); cur != p; cur = cur.getNext()) { if (predicate.apply(cur)) { return true; } } } return false; } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.<String, String>newLinkedHashMap()); nameGenerators.put( name, createNameSupplier( RenameStrategy.CONSISTENT, previousMap.get(name))); } else if (doc.isStableIdGenerator()) { nameGenerators.put( name, createNameSupplier( RenameStrategy.STABLE, previousMap.get(name))); } else if (doc.isIdGenerator()) { nameGenerators.put( name, createNameSupplier( RenameStrategy.INCONSISTENT, previousMap.get(name))); } else if (doc.isMappedIdGenerator()) { NameSupplier supplier = nameGenerators.get(name); if (supplier == null || supplier.getRenameStrategy() != RenameStrategy.MAPPED) { compiler.report(t.makeError(n, MISSING_NAME_MAP_FOR_GENERATOR)); // skip registering the name in the list of Generators if there no // mapping. return; } } else { throw new IllegalStateException("unexpected"); } idGeneratorMaps.put(name, Maps.<String, String>newLinkedHashMap()); } } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, new GatherGenerators()); if (!nameGenerators.isEmpty()) { NodeTraversal.traverse(compiler, root, new ReplaceGenerators()); } } private class ReplaceGenerators extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall()) { return; } String callName = n.getFirstChild().getQualifiedName(); NameSupplier nameGenerator = nameGenerators.get(callName); if (nameGenerator == null) { return; } if (!t.inGlobalScope() && nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) { // Warn about calls not in the global scope. compiler.report(t.makeError(n, NON_GLOBAL_ID_GENERATOR_CALL)); return; } if (nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) { for (Node ancestor : n.getAncestors()) { if (NodeUtil.isControlStructure(ancestor)) { // Warn about conditional calls. compiler.report(t.makeError(n, CONDITIONAL_ID_GENERATOR_CALL)); return; } } } Node arg = n.getFirstChild().getNext(); if (arg.isString()) { String rename = getObfuscatedName( arg, callName, nameGenerator, arg.getString()); parent.replaceChild(n, IR.string(rename)); compiler.reportCodeChange(); } else if (arg.isObjectLit()) { for (Node key : arg.children()) { String rename = getObfuscatedName( key, callName, nameGenerator, key.getString()); key.setString(rename); // Prevent standard renaming by marking the key as quoted

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> the line length and handling * indenting for pretty printing. */ @Override void startNewLine() { if (lineLength > 0) { code.append('\n'); lineIndex++; lineLength = 0; } } @Override void maybeLineBreak() { maybeCutLine(); } /** * This may start a new line if the current line is longer than the line * length threshold. */ @Override void maybeCutLine() { if (lineLength > lineLengthThreshold) { startNewLine(); } } @Override void endLine() { startNewLine(); } @Override void appendBlockStart() { append(" {"); indent++; } @Override void appendBlockEnd() { endLine(); indent--; append("}"); } @Override void listSeparator() { add(", "); maybeLineBreak(); } @Override void endFunction(boolean statementContext) { super.endFunction(statementContext); if (statementContext) { startNewLine(); } } @Override void beginCaseBody() { super.beginCaseBody(); indent++; endLine(); } @Override void endCaseBody() { super.endCaseBody(); indent--; endStatement(); } @Override void appendOp(String op, boolean binOp) { if (binOp) { if (getLastChar() != ' ' && op.charAt(0) != ',') { append(" "); } append(op); append(" "); } else { append(op); } } /** * If the body of a for loop or the then clause of an if statement has * a single statement, should it be wrapped in a block? * {@inheritDoc} */ @Override boolean shouldPreserveExtraBlocks() { // When pretty-printing, always place the statement in its own block // so it is printed on a separate line. This allows breakpoints to be // placed on the statement. return true; } /** * @return The TRY node for the specified CATCH node. */ private Node getTryForCatch(Node n) { return n.getParent().getParent(); } /** * @return Whether the a line break should be added after the specified * BLOCK. */ @Override boolean breakAfterBlockFor(Node n, boolean isStatementContext) { Preconditions.checkState(n.isBlock()); Node parent = n.getParent(); if (parent != null) { int type = parent.getType(); switch (type) { case Token.DO: // Don't break before 'while' in DO-WHILE statements. return false; case Token.FUNCTION: // FUNCTIONs are handled separately, don't break here. return false; case Token.TRY: // Don't break before catch return n != parent.getFirstChild(); case Token.CATCH: //

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } // Do not try to remove a block or an expr result. We already handle // these cases when we visit the child, and the peephole passes will // fix up the tree in more clever ways when these are removed. if (n.isExprResult() || n.isBlock()) { return; } // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. if (n.isQualifiedName() && n.getJSDocInfo() != null) { return; } boolean isResultUsed = NodeUtil.isExpressionResultUsed(n); boolean isSimpleOp = NodeUtil.isSimpleOperator(n); if (!isResultUsed && (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) { String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '" + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report( t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } } /** * Protect side-effect free nodes by making them parameters * to a extern function call. This call will be removed * after all the optimizations passes have run. */ private void protectSideEffects() { if (!problemNodes.isEmpty()) { addExtern(); for (Node n : problemNodes) { Node name = IR.name(PROTECTOR_FN).srcref(n); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node replacement = IR.call(name).srcref(n); replacement.putBooleanProp(Node.FREE_CALL, true); n.getParent().replaceChild(n, replacement); replacement.addChildToBack(n); } compiler.reportCodeChange(); } } private void addExtern() { Node name = IR.name(PROTECTOR_FN); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Prepare the AST before we do any checks or optimizations on it. * * This pass must run. It should bring the AST into a consistent state, * and add annotations where necessary. It should not make any transformations * on the tree that would lose source information, since we need that source * information for checks. * * @author johnlenz@google.com (John Lenz) */ class PrepareAst implements CompilerPass { private final AbstractCompiler compiler; private final boolean checkOnly; PrepareAst(AbstractCompiler compiler) { this(compiler, false); } PrepareAst(AbstractCompiler compiler, boolean checkOnly) { this.compiler = compiler; this.checkOnly = checkOnly; } private void reportChange() { if (checkOnly) { Preconditions.checkState(false, "normalizeNodeType constraints violated"); } } @Override public void process(Node externs, Node root) { if (checkOnly) { normalizeNodeTypes(root); } else { // Don't perform "PrepareAnnotations" when doing checks as // they currently aren't valid during sanity checks. In particular, // they DIRECT_EVAL shouldn't be applied after inlining has been // performed. if (externs != null) { NodeTraversal.traverse( compiler, externs, new PrepareAnnotations()); } if (root != null) { NodeTraversal.traverse( compiler, root, new PrepareAnnotations()); } } } /** * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code. */ private void normalizeNodeTypes(Node n) { normalizeBlocks(n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // This pass is run during the CompilerTestCase validation, so this // parent pointer check serves as a more general check. Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> final Function<TypePair, TypePair> SHEQ = new Function<TypePair, TypePair>() { @Override public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowEquality(p.typeB); } }; /** * Merging function for strict non-equality between types. */ private static final Function<TypePair, TypePair> SHNE = new Function<TypePair, TypePair>() { @Override public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowInequality(p.typeB); } }; /** * Merging function for inequality comparisons between types. */ private final Function<TypePair, TypePair> ineq = new Function<TypePair, TypePair>() { @Override public TypePair apply(TypePair p) { return new TypePair( getRestrictedWithoutUndefined(p.typeA), getRestrictedWithoutUndefined(p.typeB)); } }; /** * Creates a semantic reverse abstract interpreter. */ public SemanticReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { super(convention, typeRegistry); } @Override public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { // Check for the typeof operator. int operatorToken = condition.getType(); switch (operatorToken) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.isTypeOf() && right.isString()) { typeOfNode = left; stringNode = right; } else if (right.isTypeOf() && left.isString()) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> } } switch (operatorToken) { case Token.AND: if (outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } case Token.EQ: if (outcome) { return caseEquality(condition, blindScope, EQ); } else { return caseEquality(condition, blindScope, NE); } case Token.NE: if (outcome) { return caseEquality(condition, blindScope, NE); } else { return caseEquality(condition, blindScope, EQ); } case Token.SHEQ: if (outcome) { return caseEquality(condition, blindScope, SHEQ); } else { return caseEquality(condition, blindScope, SHNE); } case Token.SHNE: if (outcome) { return caseEquality(condition, blindScope, SHNE); } else { return caseEquality(condition, blindScope, SHEQ); } case Token.NAME: case Token.GETPROP: return caseNameOrGetProp(condition, blindScope, outcome); case Token.ASSIGN: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild().getNext(), blindScope, outcome), outcome); case Token.NOT: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), blindScope, !outcome); case Token.LE: case Token.LT: case Token.GE: case Token.GT: if (outcome) { return caseEquality(condition, blindScope, ineq); } break; case Token.INSTANCEOF: return caseInstanceOf( condition.getFirstChild(), condition.getLastChild(), blindScope, outcome); case Token.IN: if (outcome && condition.getFirstChild().isString()) { return caseIn(condition.getLastChild(), condition.getFirstChild().getString(), blindScope); } break; case Token.CASE: Node left = condition.getParent().getFirstChild(); // the switch condition Node right = condition.getFirstChild(); if (outcome) { return caseEquality(left, right, blindScope, SHEQ); } else { return caseEquality(left, right, blindScope, SHNE); } } return nextPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } private FlowScope caseEquality(Node condition

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { // Don't traverse functions that are constructors or have the @this // or @override annotation. JSDocInfo jsDoc = getFunctionJsDocInfo(n); if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.isInterface() || jsDoc.hasThisType() || jsDoc.isOverride())) { return false; } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING_KEY)) { return false; } // Don't traverse functions that are getting lent to a prototype. Node gramps = parent.getParent(); if (NodeUtil.isObjectLitKey(parent)) { JSDocInfo maybeLends = gramps.getJSDocInfo(); if (maybeLends != null && maybeLends.getLendsName() != null && maybeLends.getLendsName().endsWith(".prototype")) { return false; } } } if (parent != null && parent.isAssign()) { Node lhs = parent.getFirstChild(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { // Only traverse the right side if it's not an assignment to a prototype // property or subproperty. if (NodeUtil.isGet(lhs)) { if (lhs.isGetProp() && lhs.getLastChild().getString().equals("prototype")) { return false; } Node llhs = lhs.getFirstChild(); if (llhs.isGetProp() && llhs.getLastChild().getString().equals("prototype")) { return false; } } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis() && shouldReportThis(n)) { compiler.report(t.makeError(n, GLOBAL_THIS)); } if (n == assignLhsChild) { assignLhsChild = null; } } private boolean shouldReportThis(Node n) { Node parent = n.getParent(); if (assignLhs

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>Child != null) { // Always report a THIS on the left side of an assign. return true; } // Also report a THIS with a property access. return parent != null && NodeUtil.isGet(parent); } /** * Gets a function's JSDoc information, if it has any. Checks for a few * patterns (ellipses show where JSDoc would be): * <pre> * ... function() {} * ... x = function() {}; * var ... x = function() {}; * ... var x = function() {}; * </pre> */ private JSDocInfo getFunctionJsDocInfo(Node n) { JSDocInfo jsDoc = n.getJSDocInfo(); Node parent = n.getParent(); if (jsDoc == null) { int parentType = parent.getType(); if (parentType == Token.NAME || parentType == Token.ASSIGN) { jsDoc = parent.getJSDocInfo(); if (jsDoc == null && parentType == Token.NAME) { Node gramps = parent.getParent(); if (gramps.isVar()) { jsDoc = gramps.getJSDocInfo(); } } } } return jsDoc; } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> } } private static String format(MessageFormat format, Object... params) { return format.format(params); } /** * Only defines of literal number, string, or boolean are supported. */ private boolean isValidDefineType(JSTypeExpression expression) { JSType type = expression.evaluate(null, compiler.getTypeRegistry()); return !type.isUnknownType() && type.isSubtype( compiler.getTypeRegistry().getNativeType( JSTypeNative.NUMBER_STRING_BOOLEAN)); } /** * Finds all defines, and creates a {@link DefineInfo} data structure for * each one. * @return A map of {@link DefineInfo} structures, keyed by name. */ private Map<String, DefineInfo> collectDefines(Node root, GlobalNamespace namespace) { // Find all the global names with a @define annotation List<Name> allDefines = Lists.newArrayList(); for (Name name : namespace.getNameIndex().values()) { Ref decl = name.getDeclaration(); if (name.docInfo != null && name.docInfo.isDefine()) { // Process defines should not depend on check types being enabled, // so we look for the JSDoc instead of the inferred type. if (isValidDefineType(name.docInfo.getType())) { allDefines.add(name); } else { JSError error = JSError.make( decl.getSourceName(), decl.node, INVALID_DEFINE_TYPE_ERROR); compiler.report(error); } } else { for (Ref ref : name.getRefs()) { if (ref == decl) { // Declarations were handled above. continue; } Node n = ref.node; Node parent = ref.node.getParent(); JSDocInfo info = n.getJSDocInfo(); if (info == null && parent.isVar() && parent.hasOneChild()) { info = parent.getJSDocInfo(); } if (info != null && info.isDefine()) { allDefines.add(name); break; } } } } CollectDefines pass = new CollectDefines(compiler, allDefines); NodeTraversal.traverse(compiler, root, pass); return pass.getAllDefines(); } /** * Finds all assignments to @defines, and figures out the last value of * the @define. */ private static final class CollectDefines implements Callback { private final AbstractCompiler compiler; private final Map<String, DefineInfo> assignableDefines; private final Map<String, DefineInfo> allDefines; private final Map<Node, RefInfo> allRefInfo; // A hack that allows us to remove ASSIGN/VAR statements when // we're currently visiting one of the children of the assign. private Node lvalueToRemoveLater = null; // A stack tied to the node traversal, to keep track of whether // we're in a conditional block. If 1 is at the top, assignment to

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> boolean isAssignAllowed() { return assignAllowed.element() == 1; } /** * Tracks the given define. * * @param t The current traversal, for context. * @param name The full name for this define. * @param value The value assigned to the define. * @param valueParent The parent node of value. * @return Whether we should remove this assignment from the parse tree. */ private boolean processDefineAssignment(NodeTraversal t, String name, Node value, Node valueParent) { if (value == null || !NodeUtil.isValidDefineValue(value, allDefines.keySet())) { compiler.report( t.makeError(value, INVALID_DEFINE_INIT_ERROR, name)); } else if (!isAssignAllowed()) { compiler.report( t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name)); } else { DefineInfo info = allDefines.get(name); if (info == null) { // First declaration of this define. info = new DefineInfo(value, valueParent); allDefines.put(name, info); assignableDefines.put(name, info); } else if (info.recordAssignment(value)) { // The define was already initialized, but this is a safe // re-assignment. return true; } else { // The define was already initialized, and this is an unsafe // re-assignment. compiler.report( t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR, name, info.getReasonWhyNotAssignable())); } } return false; } /** * Gets the parent node of the value for any assignment to a Name. * For example, in the assignment * {@code var x = 3;} * the parent would be the NAME node. */ private static Node getValueParent(Ref ref) { // there are two types of declarations: VARs and ASSIGNs return ref.node.getParent() != null && ref.node.getParent().isVar() ? ref.node : ref.node.getParent(); } /** * Records the fact that because of the current node in the node traversal, * the define can't ever be assigned again. * * @param info Represents the define variable. * @param t The current traversal. */ private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) { info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE, t.getLineNumber(), t.getSourceName())); } /** * A simple data structure for associating a Ref with the name * that it references. */ private static class RefInfo { final Ref ref; final Name name; RefInfo(Ref ref, Name name) { this.ref = ref; this.name = name; } } } /** * A simple class for storing information about a define. *

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> traverse into functions or expressions. */ public abstract static class AbstractShallowStatementCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isStatementBlock(parent); } } /** * Abstract callback to visit a pruned set of nodes. */ public abstract static class AbstractNodeTypePruningCallback implements Callback { private final Set<Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include/exclude in the traversal * @param include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.inputId = null; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (inputId != null) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false,

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { inputId = NodeUtil.getInputId(root); sourceName = ""; curNode = root; pushScope(root); // null parent ensures that the shallow callbacks will traverse root traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); inputId = NodeUtil.getInputId(scopeRoot); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { String sourceFileName = getBestSourceFileName(n); if (sourceFileName == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceFileName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n" + src + "\n"; } /** * Traverses a parse tree recursively with a scope, starting with the given * root. This should only be used in the global scope. Otherwise, use * {@link #traverseAtScope}. */ void traverseWithScope(Node root, Scope s) { Preconditions.checkState(s.isGlobal()); inputId = null; sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.isFunction()) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } public AbstractCompiler getCompiler() { return compiler; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur != null) { int line = cur.getLineno(); if (line >= 0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty, but not null */ public String getSourceName() { return sourceName; } /** * Gets the current input source. */ public CompilerInput getInput() { return compiler.getInput(inputId); } /** * Gets the current input module. */ public JSModule getModule() { CompilerInput input = getInput(); return input == null ? null : input.getModule(); } /** Returns the node currently being traversed. */ public Node getCurrentNode() { return curNode; } /** * Traversal for passes that work only on changed functions. * Suppose a loopable pass P1 uses this traversal. * Then, if a function doesn't change between two runs of P1, it won't look at * the function the second time. * (We're assuming that P1 runs to a fixpoint, o/w we may miss optimizations.) * * Most changes are reported with calls to Compiler.reportCodeChange(), which * doesn't know which scope changed. We keep track of the current scope by * calling Compiler.setScope inside pushScope and popScope. * The automatic tracking can be wrong in rare cases when a pass changes scope * w/out causing a call to pushScope or popScope. It

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>'s very hard to find the * places where this happens unless a bug is triggered. * Passes that do cross-scope modifications call * Compiler.reportChangeToEnclosingScope(Node n). */ public static void traverseChangedFunctions( AbstractCompiler compiler, FunctionCallback callback) { final AbstractCompiler comp = compiler; final FunctionCallback cb = callback; final Node jsRoot = comp.getJsRoot(); NodeTraversal t = new NodeTraversal(comp, new AbstractPreOrderCallback() { @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node p) { if ((n == jsRoot || n.isFunction()) && comp.hasScopeChanged(n)) { cb.visit(comp, n); } return true; } }); t.traverse(jsRoot); } /** * Traverses a node recursively. */ public static void traverse( AbstractCompiler compiler, Node root, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverse(root); } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } public static void traverseRoots( AbstractCompiler compiler, Callback cb, Node ... roots) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { inputId = n.getInputId(); sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) { return; } if (type == Token.FUNCTION) { traverseFunction(n, parent); } else { for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } } curNode = n; callback.visit(this, n, parent); } /** Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.isFunction()); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> 'var e = 1' would be rewritten as 'e = 1'. // TODO(johnlenz): Introduce a separate scope for catch nodes. removeDuplicateDeclarations(externs, root); new PropagateConstantAnnotationsOverVars(compiler, assertOnChange) .process(externs, root); FindExposeAnnotations findExposeAnnotations = new FindExposeAnnotations(); NodeTraversal.traverse(compiler, root, findExposeAnnotations); if (!findExposeAnnotations.exposedProperties.isEmpty()) { NodeTraversal.traverse(compiler, root, new RewriteExposedProperties( findExposeAnnotations.exposedProperties)); } if (!compiler.getLifeCycleStage().isNormalized()) { compiler.setLifeCycleStage(LifeCycleStage.NORMALIZED); } } /** * Find all the @expose annotations. */ private static class FindExposeAnnotations extends AbstractPostOrderCallback { private final Set<String> exposedProperties = Sets.newHashSet(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isExprAssign(n)) { Node assign = n.getFirstChild(); Node lhs = assign.getFirstChild(); if (lhs.isGetProp() && isMarkedExpose(assign)) { exposedProperties.add(lhs.getLastChild().getString()); } } else if (n.isStringKey() && isMarkedExpose(n)) { exposedProperties.add(n.getString()); } else if (n.isGetProp() && n.getParent().isExprResult() && isMarkedExpose(n)) { exposedProperties.add(n.getLastChild().getString()); } } private boolean isMarkedExpose(Node n) { JSDocInfo info = n.getJSDocInfo(); return info != null && info.isExpose(); } } /** * Rewrite all exposed properties in [] form. */ private class RewriteExposedProperties extends AbstractPostOrderCallback { private final Set<String> exposedProperties; RewriteExposedProperties(Set<String> exposedProperties) { this.exposedProperties = exposedProperties; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp()) { String propName = n.getLastChild().getString(); if (exposedProperties.contains(propName)) { Node obj = n.removeFirstChild(); Node prop = n.removeFirstChild(); n.getParent().replaceChild(n, IR.getelem(obj, prop)); compiler.reportCodeChange(); } } else if (n.isStringKey()) { String propName = n.getString(); if (exposedProperties.contains(propName)) { n.setQuotedString(); compiler.reportCodeChange(); } } } } /** * Propagate constant annotations over the Var graph. */ static class PropagateConstantAnnotationsOverVars extends AbstractPostOrderCallback implements CompilerPass { private

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> final AbstractCompiler compiler; private final boolean assertOnChange; PropagateConstantAnnotationsOverVars( AbstractCompiler compiler, boolean forbidChanges) { this.compiler = compiler; this.assertOnChange = forbidChanges; } @Override public void process(Node externs, Node root) { new NodeTraversal(compiler, this).traverseRoots(externs, root); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Note: Constant properties annotations are not propagated. if (n.isName()) { if (n.getString().isEmpty()) { return; } JSDocInfo info = null; // Find the JSDocInfo for a top-level variable. Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } boolean shouldBeConstant = (info != null && info.isConstant()) || NodeUtil.isConstantByConvention( compiler.getCodingConvention(), n, parent); boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (shouldBeConstant && !isMarkedConstant) { if (assertOnChange) { String name = n.getString(); throw new IllegalStateException( "Unexpected const change.\n" + " name: "+ name + "\n" + " parent:" + n.getParent().toStringTree()); } n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } } /** * Walk the AST tree and verify that constant names are used consistently. */ static class VerifyConstants extends AbstractPostOrderCallback implements CompilerPass { final private AbstractCompiler compiler; final private boolean checkUserDeclarations; VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) { this.compiler = compiler; this.checkUserDeclarations = checkUserDeclarations; } @Override public void process(Node externs, Node root) { Node externsAndJs = root.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState(externsAndJs.hasChild(externs)); NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } private Map<String, Boolean> constantMap = Maps.newHashMap(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { String name = n.getString(); if (n.getString().isEmpty()) { return; } boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (checkUserDeclarations) { boolean expectedConst = false; CodingConvention convention = compiler.getCodingConvention(); if (NodeUtil.isConstantName(n) || NodeUtil.isConstantByConvention(convention, n, parent)) { expectedConst = true; } else { expected

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> parent.replaceChild(n, n.removeFirstChild()); break; } } /** * Mark names and properties that are constants by convention. */ private void annotateConstantsByConvention(Node n, Node parent) { Preconditions.checkState( n.isName() || n.isString() || n.isStringKey() || n.isGetterDef() || n.isSetterDef()); // There are only two cases where a string token // may be a variable reference: The right side of a GETPROP // or an OBJECTLIT key. boolean isObjLitKey = NodeUtil.isObjectLitKey(n); boolean isProperty = isObjLitKey || (parent.isGetProp() && parent.getLastChild() == n); if (n.isName() || isProperty) { boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (!isMarkedConstant && NodeUtil.isConstantByConvention( compiler.getCodingConvention(), n, parent)) { if (assertOnChange) { String name = n.getString(); throw new IllegalStateException( "Unexpected const change.\n" + " name: "+ name + "\n" + " parent:" + n.getParent().toStringTree()); } n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } /** * Rewrite named unhoisted functions declarations to a known * consistent behavior so we don't to different logic paths for the same * code. From: * function f() {} * to: * var f = function () {}; */ private void normalizeFunctionDeclaration(Node n) { Preconditions.checkState(n.isFunction()); if (!NodeUtil.isFunctionExpression(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { rewriteFunctionDeclaration(n); } } /** * Rewrite the function declaration from: * function x() {} * FUNCTION * NAME * LP * BLOCK * to: * var x = function() {}; * VAR * NAME * FUNCTION * NAME (w/ empty string) * LP * BLOCK */ private void rewriteFunctionDeclaration(Node n) { // Prepare a spot for the function. Node oldNameNode = n.getFirstChild(); Node fnNameNode = oldNameNode.cloneNode(); Node var = IR.var(fnNameNode).srcref(n); // Prepare the function oldNameNode.setString(""); // Move the function Node parent = n.getParent(); parent.replaceChild(n, var); fnNameNode.addChildToFront(n); reportCodeChange("Function declaration"); } /** * Do normalizations that introduce new siblings or parents. */ private void doStatementNormalizations(Node n) { if (n.isLabel()) { normalizeLabels(n); }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.getParent().replaceChild(first, name); insertBeforeParent.addChildBefore(newStatement, insertBefore); reportCodeChange("FOR-IN var declaration"); } } else if (!c.getFirstChild().isEmpty()) { Node init = c.getFirstChild(); Node empty = IR.empty(); empty.copyInformationFrom(c); c.replaceChild(init, empty); Node newStatement; // Only VAR statements, and expressions are allowed, // but are handled differently. if (init.isVar()) { newStatement = init; } else { newStatement = NodeUtil.newExpr(init); } insertBeforeParent.addChildBefore(newStatement, insertBefore); reportCodeChange("FOR initializer"); } break; } } } /** * Split a var node such as: * var a, b; * into individual statements: * var a; * var b; * @param n The whose children we should inspect. */ private void splitVarDeclarations(Node n) { for (Node next, c = n.getFirstChild(); c != null; c = next) { next = c.getNext(); if (c.isVar()) { if (assertOnChange && !c.hasChildren()) { throw new IllegalStateException("Empty VAR node."); } while (c.getFirstChild() != c.getLastChild()) { Node name = c.getFirstChild(); c.removeChild(name); Node newVar = IR.var(name).srcref(n); n.addChildBefore(newVar, c); reportCodeChange("VAR with multiple children"); } } } } /** * Move all the functions that are valid at the execution of the first * statement of the function to the beginning of the function definition. */ private void moveNamedFunctions(Node functionBody) { Preconditions.checkState( functionBody.getParent().isFunction()); Node previous = null; Node current = functionBody.getFirstChild(); // Skip any declarations at the beginning of the function body, they // are already in the right place. while (current != null && NodeUtil.isFunctionDeclaration(current)) { previous = current; current = current.getNext(); } // Find any remaining declarations and move them. Node insertAfter = previous; while (current != null) { // Save off the next node as the current node maybe removed. Node next = current.getNext(); if (NodeUtil.isFunctionDeclaration(current)) { // Remove the declaration from the body. Preconditions.checkNotNull(previous); functionBody.removeChildAfter(previous); // Read the function at the top of the function body (after any // previous declarations). insertAfter = addToFront(functionBody, current, insertAfter); reportCodeChange("Move function declaration not at top of function"); } else { // Update the previous only if the current node hasn't been moved. previous = current; } current = next;

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> } } /** * @param after The child node to insert the newChild after, or null if * newChild should be added to the front of parent's child list. * @return The inserted child node. */ private Node addToFront(Node parent, Node newChild, Node after) { if (after == null) { parent.addChildToFront(newChild); } else { parent.addChildAfter(newChild, after); } return newChild; } } /** * Remove duplicate VAR declarations. */ private void removeDuplicateDeclarations(Node externs, Node root) { Callback tickler = new ScopeTicklingCallback(); ScopeCreator scopeCreator = new SyntacticScopeCreator( compiler, new DuplicateDeclarationHandler()); NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator); t.traverseRoots(externs, root); } /** * ScopeCreator duplicate declaration handler. */ private final class DuplicateDeclarationHandler implements SyntacticScopeCreator.RedeclarationHandler { private Set<Var> hasOkDuplicateDeclaration = Sets.newHashSet(); /** * Remove duplicate VAR declarations encountered discovered during * scope creation. */ @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); Var v = s.getVar(name); if (v != null && s.isGlobal()) { // We allow variables to be duplicate declared if one // declaration appears in source and the other in externs. // This deals with issues where a browser built-in is declared // in one browser but not in another. if (v.isExtern() && !input.isExtern()) { if (hasOkDuplicateDeclaration.add(v)) { return; } } } // If name is "arguments", Var maybe null. if (v != null && v.getParentNode().isCatch()) { // Redeclaration of a catch expression variable is hard to model // without support for "with" expressions. // The ECMAScript spec (section 12.14), declares that a catch // "catch (e) {}" is handled like "with ({'e': e}) {}" so that // "var e" would refer to the scope variable, but any following // reference would still refer to "e" of the catch expression. // Until we have support for this disallow it. // Currently the Scope object adds the catch expression to the // function scope, which is technically not true but a good // approximation for most uses. // TODO(johnlenz): Consider improving how scope handles catch // expression. // Use the name of the var before it was made unique. name = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName( name); compiler.report( JSError.make( input.getName(), n, CATCH

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>_BLOCK_VAR_ERROR, name)); } else if (v != null && parent.isFunction()) { if (v.getParentNode().isVar()) { s.undeclare(v); s.declare(name, n, n.getJSType(), v.input); replaceVarWithAssignment(v.getNameNode(), v.getParentNode(), v.getParentNode().getParent()); } } else if (parent.isVar()) { Preconditions.checkState(parent.hasOneChild()); replaceVarWithAssignment(n, parent, parent.getParent()); } } /** * Remove the parent VAR. There are three cases that need to be handled: * 1) "var a = b;" which is replaced with "a = b" * 2) "label:var a;" which is replaced with "label:;". Ideally, the * label itself would be removed but that is not possible in the * context in which "onRedeclaration" is called. * 3) "for (var a in b) ..." which is replaced with "for (a in b)..." * Cases we don't need to handle are VARs with multiple children, * which have already been split into separate declarations, so there * is no need to handle that here, and "for (var a;;);", which has * been moved out of the loop. * The result of this is that in each case the parent node is replaced * which is generally dangerous in a traversal but is fine here with * the scope creator, as the next node of interest is the parent's * next sibling. */ private void replaceVarWithAssignment(Node n, Node parent, Node gramps) { if (n.hasChildren()) { // The * is being initialize, preserve the new value. parent.removeChild(n); // Convert "var name = value" to "name = value" Node value = n.getFirstChild(); n.removeChild(value); Node replacement = IR.assign(n, value); replacement.copyInformationFrom(parent); gramps.replaceChild(parent, NodeUtil.newExpr(replacement)); } else { // It is an empty reference remove it. if (NodeUtil.isStatementBlock(gramps)) { gramps.removeChild(parent); } else if (gramps.isFor()) { // This is the "for (var a in b)..." case. We don't need to worry // about initializers in "for (var a;;)..." as those are moved out // as part of the other normalizations. parent.removeChild(n); gramps.replaceChild(parent, n); } else { Preconditions.checkState(gramps.isLabel()); // We should never get here. LABELs with a single VAR statement should // already have been normalized to have a BLOCK. throw new IllegalStateException("Unexpected LABEL"); } } reportCode

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>() || node.isFunction() || node.isName() || NodeUtil.isGet(node) || NodeUtil.isObjectLitKey(node) || (node.isString() && NodeUtil.isGet(node.getParent())); } }; } public static class LocationMapping { final String prefix; final String replacement; public LocationMapping(String prefix, String replacement) { this.prefix = prefix; this.replacement = replacement; } } private final SourceMapGenerator generator; private List<LocationMapping> prefixMappings = Collections.emptyList(); private final Map<String, String> sourceLocationFixupCache = Maps.newHashMap(); private SourceMap(SourceMapGenerator generator) { this.generator = generator; } public void addMapping( Node node, FilePosition outputStartPosition, FilePosition outputEndPosition) { String sourceFile = node.getSourceFileName(); // If the node does not have an associated source file or // its line number is -1, then the node does not have sufficient // information for a mapping to be useful. if (sourceFile == null || node.getLineno() < 0) { return; } sourceFile = fixupSourceLocation(sourceFile); String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP); // Strangely, Rhino source lines are one based but columns are // zero based. // We don't change this for the v1 or v2 source maps but for // v3 we make them both 0 based. int lineBaseOffset = 1; if (generator instanceof SourceMapGeneratorV1 || generator instanceof SourceMapGeneratorV2) { lineBaseOffset = 0; } generator.addMapping( sourceFile, originalName, new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()), outputStartPosition, outputEndPosition); } /** * @param sourceFile The source file location to fixup. * @return a remapped source file. */ private String fixupSourceLocation(String sourceFile) { if (prefixMappings.isEmpty()) { return sourceFile; } String fixed = sourceLocationFixupCache.get(sourceFile); if (fixed != null) { return fixed; } // Replace the first prefix found with its replacement for (LocationMapping mapping : prefixMappings) { if (sourceFile.startsWith(mapping.prefix)) { fixed = mapping.replacement + sourceFile.substring( mapping.prefix.length()); break; } } // If none of the mappings match then use the original file path. if (fixed == null) { fixed = sourceFile; } sourceLocationFixupCache.put(sourceFile, fixed); return fixed; } public void appendTo(Appendable out, String name) throws IOException { generator.appendTo(out, name); } public void reset()

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> (maybeDecl.isVarDeclaration()) { Preconditions.checkState(!maybeDecl.isInitializingDeclaration()); Reference maybeInit = references.get(index); if (maybeInit.isSimpleAssignmentToName()) { return true; } } } return false; } /** * @return The reference that provides the value for the variable at the * time of the first read, if known, otherwise null. * * This is either the variable declaration ("var a = ...") or first * reference following the declaration if it is an assignment. */ Reference getInitializingReference() { if (isInitializingDeclarationAt(0)) { return references.get(0); } else if (isInitializingAssignmentAt(1)) { return references.get(1); } return null; } /** * Constants are allowed to be defined after their first use. */ Reference getInitializingReferenceForConstants() { int size = references.size(); for (int i = 0; i < size; i++) { if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) { return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { Reference assignment = null; int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { if (assignment == null) { assignment = ref; } else { return null; } } } return assignment; } /** * @return Whether the variable is never assigned a value. */ boolean isNeverAssigned() { int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { return false; } } return true; } boolean firstReferenceIsAssigningDeclaration() { int size = references.size(); if (size > 0 && references.get(0).isInitializingDeclaration()) { return true; } return false; } } /**

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> * Represents a single declaration or reference to a variable. */ static final class Reference implements StaticReference<JSType> { private static final Set<Integer> DECLARATION_PARENTS = ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH); private final Node nameNode; private final BasicBlock basicBlock; private final Scope scope; private final InputId inputId; private final StaticSourceFile sourceFile; Reference(Node nameNode, NodeTraversal t, BasicBlock basicBlock) { this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId()); } // Bleeding functions are weird, because the declaration does // not appear inside their scope. So they need their own constructor. static Reference newBleedingFunction(NodeTraversal t, BasicBlock basicBlock, Node func) { return new Reference(func.getFirstChild(), basicBlock, t.getScope(), t.getInput().getInputId()); } /** * Creates a variable reference in a given script file name, used in tests. * * @return The created reference. */ @VisibleForTesting static Reference createRefForTest(CompilerInput input) { return new Reference(new Node(Token.NAME), null, null, input.getInputId()); } private Reference(Node nameNode, BasicBlock basicBlock, Scope scope, InputId inputId) { this.nameNode = nameNode; this.basicBlock = basicBlock; this.scope = scope; this.inputId = inputId; this.sourceFile = nameNode.getStaticSourceFile(); } /** * Makes a copy of the current reference using a new Scope instance. */ Reference cloneWithNewScope(Scope newScope) { return new Reference(nameNode, basicBlock, newScope, inputId); } @Override public Var getSymbol() { return scope.getVar(nameNode.getString()); } @Override public Node getNode() { return nameNode; } public InputId getInputId() { return inputId; } @Override public StaticSourceFile getSourceFile() { return sourceFile; } boolean isDeclaration() { Node parent = getParent(); Node grandparent = parent.getParent(); return DECLARATION_PARENTS.contains(parent.getType()) || parent.isParamList() && grandparent.isFunction(); } boolean isVarDeclaration() { return getParent().isVar(); } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(getParent()); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() { // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && !getParent().isVar() || nameNode.getFirstChild() != null; } /** * @return For an assignment, variable

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { Node parent = getParent(); return (parent.isFunction()) ? parent : NodeUtil.getAssignedValue(nameNode); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return getNode().getParent(); } Node getGrandparent() { Node parent = getParent(); return parent == null ? null : parent.getParent(); } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { Node parent = getParent(); return parent.isAssign() && parent.getFirstChild() == nameNode; } boolean isLvalue() { Node parent = getParent(); int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); } Scope getScope() { return scope; } } /** * Represents a section of code that is uninterrupted by control structures * (conditional or iterative logic). */ static final class BasicBlock { private final BasicBlock parent; /** * Determines whether the block may not be part of the normal control flow, * but instead "hoisted" to the top of the scope. */ private final boolean isHoisted; /** * Whether this block denotes a function scope. */ private final boolean isFunction; /** * Whether this block denotes a loop. */ private final boolean isLoop; /** * Creates a new block. * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; // only named functions may be hoisted. this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root); this.isFunction = root.isFunction(); if (root.getParent() != null) { int pType = root.getParent().getType(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR; } else { this.isLoop = false; } } BasicBlock getParent() { return parent; } /** * Determines whether this block is equivalent to the very first block that * is created when reference collection traversal enters global scope. Note * that when traversing a single script in a hot-swap fashion a new instance * of

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> {@code BasicBlock} is created. * * @return true if this is global scope block. */ boolean isGlobalScopeBlock() { return getParent() == null; } /** * Determines whether this block is guaranteed to begin executing before * the given block does. */ boolean provablyExecutesBefore(BasicBlock thatBlock) { // If thatBlock is a descendant of this block, and there are no hoisted // blocks between them, then this block must start before thatBlock. BasicBlock currentBlock; for (currentBlock = thatBlock; currentBlock != null && currentBlock != this; currentBlock = currentBlock.getParent()) { if (currentBlock.isHoisted) { return false; } } if (currentBlock == this) { return true; } if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) { return true; } return false; } } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> process(Node externsRoot, Node jsRoot) { Preconditions.checkNotNull(scopeCreator); Preconditions.checkNotNull(topScope); Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); if (externsRoot != null) { check(externsRoot, true); } check(jsRoot, false); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo.process(node, null); } else { inferJSDocInfo.process(null, node); } } private void checkNoTypeCheckSection(Node n, boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } @Override public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final Scope outerScope = t.getScope(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false)

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> if (rightType.isStruct()) { report(t, right, IN_USED_WITH_STRUCT); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.INSTANCEOF: left = n.getFirstChild(); right = n.getLastChild(); rightType = getJSType(right).restrictByNotNullOrUndefined(); validator.expectAnyObject( t, left, getJSType(left), "deterministic instanceof yields false"); validator.expectActualObject( t, right, rightType, "instanceof requires an object"); ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.ASSIGN: visitAssign(t, n); typeable = false; break; case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_SUB: case Token.ASSIGN_ADD: case Token.ASSIGN_MUL: checkPropCreation(t, n.getFirstChild()); // fall through case Token.LSH: case Token.RSH: case Token.URSH: case Token.DIV: case Token.MOD: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.SUB: case Token.ADD: case Token.MUL: visitBinaryOperator(n.getType(), t, n); break; case Token.DELPROP: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.CASE: JSType switchType = getJSType(parent.getFirstChild()); JSType caseType = getJSType(n.getFirstChild()); validator.expectSwitchMatchesCase(t, n, switchType, caseType); typeable = false; break; case Token.WITH: { Node child = n.getFirstChild(); childType = getJSType(child); validator.expectObject(t, child, childType, "with requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.LABEL_NAME: case Token.SWITCH: case Token.BREAK: case Token.CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT_CASE: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.IF: case Token.WHILE: typeable = false;

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> lookups, then check inheritance anyway with the unknown type. checkPropertyInheritanceOnGetpropAssign( t, assign, object, pname, info, getNativeType(UNKNOWN_TYPE)); } // Check qualified name sets to 'object' and 'object.property'. // This can sometimes handle cases when the type of 'object' is not known. // e.g., // var obj = createUnknownType(); // /** @type {number} */ obj.foo = true; JSType leftType = getJSType(lvalue); if (lvalue.isQualifiedName()) { // variable with inferred type case Var var = t.getScope().getVar(lvalue.getQualifiedName()); if (var != null) { if (var.isTypeInferred()) { return; } if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() && t.getScope() != var.getScope()) { // Don't look at "this.foo" variables from other scopes. return; } if (var.getType() != null) { leftType = var.getType(); } } } // Fall through case for arbitrary LHS and arbitrary RHS. Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } /** * After a struct object is created, we can't add new properties to it, with * one exception. We allow creation of "static" properties like * Foo.prototype.bar = baz; * where Foo.prototype is a struct, if the assignment happens at the top level * and the constructor Foo is defined in the same file. */ private void checkPropCreation(NodeTraversal t, Node lvalue) { if (lvalue.isGetProp()) { Node obj = lvalue.getFirstChild(); Node prop = lvalue.getLastChild(); JSType objType = getJSType(obj); String pname = prop.getString(); if (!objType.isStruct() || objType.hasProperty(pname)) { return; } Scope s = t.getScope(); if (obj.isThis() && getJSType(s.getRootNode()).isConstructor()) { return; } // Prop created outside ctor, check that it's a static prop Node assgnExp = lvalue.getParent(); Node assgnStm = assgnExp.getParent(); if (objType instanceof ObjectType && s.isGlobal() && NodeUtil.isPrototypePropertyDeclaration(assgnStm)) { ObjectType instance = objType.toObjectType().getOwnerFunction().getInstanceType(); String file = lvalue.getSourceFileName(); Node ctor = instance.getConstructor().getSource(); if (ctor != null && ctor.getSourceFileName().equals(file

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> childType, String propName, NodeTraversal t, Node n) { // If the property type is unknown, check the object type to see if it // can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for // properties where we've checked that it exists, or for properties on // objects that aren't in this binary). JSType propType = getJSType(n); if (propType.isEquivalentTo(typeRegistry.getNativeType(UNKNOWN_TYPE))) { childType = childType.autobox(); ObjectType objectType = ObjectType.cast(childType); if (objectType != null) { // We special-case object types so that checks on enums can be // much stricter, and so that we can use hasProperty (which is much // faster in most cases). if (!objectType.hasProperty(propName) || objectType.isEquivalentTo( typeRegistry.getNativeType(UNKNOWN_TYPE))) { if (objectType instanceof EnumType) { report(t, n, INEXISTENT_ENUM_ELEMENT, propName); } else { checkPropertyAccessHelper(objectType, propName, t, n); } } } else { checkPropertyAccessHelper(childType, propName, t, n); } } } private void checkPropertyAccessHelper(JSType objectType, String propName, NodeTraversal t, Node n) { if (!objectType.isEmptyType() && reportMissingProperties && (!isPropertyTest(n) || objectType.isStruct())) { if (!typeRegistry.canPropertyBeDefined(objectType, propName)) { report(t, n, INEXISTENT_PROPERTY, propName, validator.getReadableJSTypeName(n.getFirstChild(), true)); } } } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().isOr() && parent.getParent().getFirstChild() == parent; case Token.CAST: return isPropertyTest(parent); } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>getImplementedInterfaces()) { boolean badImplementedType = false; ObjectType baseInterfaceObj = ObjectType.cast(baseInterface); if (baseInterfaceObj != null) { FunctionType interfaceConstructor = baseInterfaceObj.getConstructor(); if (interfaceConstructor != null && !interfaceConstructor.isInterface()) { badImplementedType = true; } } else { badImplementedType = true; } if (badImplementedType) { report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName); } } // check properties validator.expectAllInterfaceProperties(t, n, functionType); } } else if (functionType.isInterface()) { // Interface must extend only interfaces for (ObjectType extInterface : functionType.getExtendedInterfaces()) { if (extInterface.getConstructor() != null && !extInterface.getConstructor().isInterface()) { compiler.report( t.makeError(n, CONFLICTING_EXTENDED_TYPE, "interface", functionPrivateName)); } } // Check whether the extended interfaces have any conflicts if (functionType.getExtendedInterfacesCount() > 1) { // Only check when extending more than one interfaces HashMap<String, ObjectType> properties = new HashMap<String, ObjectType>(); HashMap<String, ObjectType> currentProperties = new HashMap<String, ObjectType>(); for (ObjectType interfaceType : functionType.getExtendedInterfaces()) { currentProperties.clear(); checkInterfaceConflictProperties(t, n, functionPrivateName, properties, currentProperties, interfaceType); properties.putAll(currentProperties); } } } } /** * Visits a CALL node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitCall(NodeTraversal t, Node n) { Node child = n.getFirstChild(); JSType childType = getJSType(child).restrictByNotNullOrUndefined(); if (!childType.canBeCalled()) { report(t, n, NOT_CALLABLE, childType.toString()); ensureTyped(t, n); return; } // A couple of types can be called as if they were functions. // If it is a function type, then validate parameters. if (childType.isFunctionType()) { FunctionType functionType = childType.toMaybeFunctionType(); boolean isExtern = false; JSDocInfo functionJSDocInfo = functionType.getJSDocInfo(); if (functionJSDocInfo != null && functionJSDocInfo.getAssociatedNode() != null) { isExtern = functionJSDocInfo.getAssociatedNode().isFromExterns(); } // Non-native constructors should not be called directly // unless they specify a return type and are defined // in an extern. if (functionType.is

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>Constructor() && !functionType.isNativeObjectType() && (functionType.getReturnType().isUnknownType() || functionType.getReturnType().isVoidType() || !isExtern)) { report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString()); } // Functions with explicit 'this' types must be called in a GETPROP // or GETELEM. if (functionType.isOrdinaryFunction() && !functionType.getTypeOfThis().isUnknownType() && !(functionType.getTypeOfThis().toObjectType() != null && functionType.getTypeOfThis().toObjectType().isNativeObjectType()) && !(child.isGetElem() || child.isGetProp())) { report(t, n, EXPECTED_THIS_TYPE, functionType.toString()); } visitParameterList(t, n, functionType); ensureTyped(t, n, functionType.getReturnType()); } else { ensureTyped(t, n); } // TODO(nicksantos): Add something to check for calls of RegExp objects, // which is not supported by IE. Either say something about the return type // or warn about the non-portability of the call or both. } /** * Visits the parameters of a CALL or a NEW node. */ private void visitParameterList(NodeTraversal t, Node call, FunctionType functionType) { Iterator<Node> arguments = call.children().iterator(); arguments.next(); // skip the function name Iterator<Node> parameters = functionType.getParameters().iterator(); int ordinal = 0; Node parameter = null; Node argument = null; while (arguments.hasNext() && (parameters.hasNext() || parameter != null && parameter.isVarArgs())) { // If there are no parameters left in the list, then the while loop // above implies that this must be a var_args function. if (parameters.hasNext()) { parameter = parameters.next(); } argument = arguments.next(); ordinal++; validator.expectArgumentMatchesParameter(t, argument, getJSType(argument), getJSType(parameter), call, ordinal); } int numArgs = call.getChildCount() - 1; int minArgs = functionType.getMinArguments(); int maxArgs = functionType.getMaxArguments(); if (minArgs > numArgs || maxArgs < numArgs) { report(t, call, WRONG_ARGUMENT_COUNT, validator.getReadableJSTypeName(call.getFirstChild(), false), String.valueOf(numArgs), String.valueOf(minArgs), maxArgs != Integer.MAX_VALUE ? " and no more than " + maxArgs + " argument(s)" : ""); } } /** * Visits a RETURN node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * Models an assignment that defines a variable and the removal of it. * */ class DefinitionsRemover { /** * @return an {@link Definition} object if the node contains a definition or * {@code null} otherwise. */ static Definition getDefinition(Node n, boolean isExtern) { // TODO(user): Since we have parent pointers handy. A lot of constructors // can be simplified. // This logic must match #isDefinitionNode Node parent = n.getParent(); if (parent == null) { return null; } if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) { return new VarDefinition(n, isExtern); } else if (parent.isFunction() && parent.getFirstChild() == n) { if (!NodeUtil.isFunctionExpression(parent)) { return new NamedFunctionDefinition(parent, isExtern); } else if (!n.getString().equals("")) { return new FunctionExpressionDefinition(parent, isExtern); } } else if (parent.isAssign() && parent.getFirstChild() == n) { return new AssignmentDefinition(parent, isExtern); } else if (NodeUtil.isObjectLitKey(n)) { return new ObjectLiteralPropertyDefinition(parent, n, n.getFirstChild(), isExtern); } else if (parent.isParamList()) { Node function = parent.getParent(); return new FunctionArgumentDefinition(function, n, isExtern); } return null; } /** * @return Whether a definition object can be created. */ static boolean isDefinitionNode(Node n) { // This logic must match #getDefinition Node parent = n.getParent(); if (parent == null) { return false; } if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) { return true; } else if (parent.isFunction() && parent.getFirstChild

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>"); } } /** * Represents an name-only external definition. The definition's * RHS is missing. */ static final class ExternalNameOnlyDefinition extends IncompleteDefinition { ExternalNameOnlyDefinition(Node lValue) { super(lValue, true); } @Override public void performRemove() { throw new IllegalArgumentException( "Can't remove external name-only definition"); } } /** * Represents a function formal parameter. The definition's RHS is missing. */ static final class FunctionArgumentDefinition extends IncompleteDefinition { FunctionArgumentDefinition(Node function, Node argumentName, boolean inExterns) { super(argumentName, inExterns); Preconditions.checkArgument(function.isFunction()); Preconditions.checkArgument(argumentName.isName()); } @Override public void performRemove() { throw new IllegalArgumentException( "Can't remove a FunctionArgumentDefinition"); } } /** * Represents a function declaration or function expression. */ abstract static class FunctionDefinition extends Definition { protected final Node function; FunctionDefinition(Node node, boolean inExterns) { super(inExterns); Preconditions.checkArgument(node.isFunction()); function = node; } @Override public Node getLValue() { return function.getFirstChild(); } @Override public Node getRValue() { return function; } } /** * Represents a function declaration without assignment node such as * {@code function foo()}. */ static final class NamedFunctionDefinition extends FunctionDefinition { NamedFunctionDefinition(Node node, boolean inExterns) { super(node, inExterns); } @Override public void performRemove() { function.detachFromParent(); } } /** * Represents a function expression that acts as a RHS. The defined * name is only reachable from within the function. */ static final class FunctionExpressionDefinition extends FunctionDefinition { FunctionExpressionDefinition(Node node, boolean inExterns) { super(node, inExterns); Preconditions.checkArgument( NodeUtil.isFunctionExpression(node)); } @Override public void performRemove() { // replace internal name with "" function.replaceChild(function.getFirstChild(), IR.name("")); } } /** * Represents a declaration within an assignment. */ static final class AssignmentDefinition extends Definition { private final Node assignment; AssignmentDefinition(Node node, boolean inExterns) { super(inExterns); Preconditions.checkArgument(node.isAssign()); assignment = node; } @Override public void performRemove() { // A simple assignment. foo = bar() -> bar(); Node parent = assignment.getParent(); Node last = assignment.getLastChild(); assignment.removeChild(last); parent.replaceChild(assignment, last); } @Override public Node getLValue() { return assignment.getFirstChild(); } @Override

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> public Node getRValue() { return assignment.getLastChild(); } } /** * Represents member declarations using a object literal. * Example: var x = { e : function() { } }; */ static final class ObjectLiteralPropertyDefinition extends Definition { private final Node literal; private final Node name; private final Node value; ObjectLiteralPropertyDefinition(Node lit, Node name, Node value, boolean isExtern) { super(isExtern); this.literal = lit; this.name = name; this.value = value; } @Override public void performRemove() { literal.removeChild(name); } @Override public Node getLValue() { // TODO(user) revisit: object literal definitions are an example // of definitions whose LHS doesn't correspond to a node that // exists in the AST. We will have to change the return type of // getLValue sooner or later in order to provide this added // flexibility. switch (name.getType()) { case Token.SETTER_DEF: case Token.GETTER_DEF: case Token.STRING_KEY: // TODO(johnlenz): return a GETELEM for quoted strings. return IR.getprop( IR.objectlit(), IR.string(name.getString())); default: throw new IllegalStateException("unexpected"); } } @Override public Node getRValue() { return value; } } /** * Represents a VAR declaration with an assignment. */ static final class VarDefinition extends Definition { private final Node name; VarDefinition(Node node, boolean inExterns) { super(inExterns); Preconditions.checkArgument(NodeUtil.isVarDeclaration(node)); Preconditions.checkArgument(node.hasChildren(), "VAR Declaration of %sshould be assigned a value.", node.getString()); name = node; } @Override public void performRemove() { Node var = name.getParent(); Preconditions.checkState(var.getFirstChild() == var.getLastChild(), "AST should be normalized first"); Node parent = var.getParent(); Node rValue = name.removeFirstChild(); Preconditions.checkState(!parent.isFor()); parent.replaceChild(var, NodeUtil.newExpr(rValue)); } @Override public Node getLValue() { return name; } @Override public Node getRValue() { return name.getFirstChild(); } } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> fileoverview. */ private boolean handlePossibleFileOverviewJsDoc( JsDocInfoParser jsDocParser) { if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) { fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo(); return true; } return false; } private void handlePossibleFileOverviewJsDoc(Comment comment, Node irNode) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode); parsedComments.add(comment); handlePossibleFileOverviewJsDoc(jsDocParser); } private JSDocInfo handleJsDoc(AstNode node, Node irNode) { Comment comment = node.getJsDocNode(); if (comment != null) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode); parsedComments.add(comment); if (!handlePossibleFileOverviewJsDoc(jsDocParser)) { JSDocInfo info = jsDocParser.retrieveAndResetParsedJSDocInfo(); if (info != null) { validateTypeAnnotations(info, node); } return info; } } return null; } private void validateTypeAnnotations(JSDocInfo info, AstNode node) { if (info.hasType()) { boolean valid = false; switch (node.getType()) { // Casts are valid case com.google.javascript.rhino.head.Token.LP: valid = node instanceof ParenthesizedExpression; break; // Variable declarations are valid case com.google.javascript.rhino.head.Token.VAR: valid = true; break; // Function declarations are valid case com.google.javascript.rhino.head.Token.FUNCTION: FunctionNode fnNode = (FunctionNode) node; valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT; break; // Object literal properties and catch declarations are valid. case com.google.javascript.rhino.head.Token.NAME: valid = node.getParent() instanceof ObjectProperty || node.getParent() instanceof CatchClause || node.getParent() instanceof FunctionNode; break; // Object literal properties are valid case com.google.javascript.rhino.head.Token.GET: case com.google.javascript.rhino.head.Token.SET: case com.google.javascript.rhino.head.Token.NUMBER: case com.google.javascript.rhino.head.Token.STRING: valid = node.getParent() instanceof ObjectProperty; break; // Property assignments are valid, if at the root of an expression. case com.google.javascript.rhino.head.Token.ASSIGN: if (node instanceof Assignment) { valid = isExprStmt(node.getParent()) && isPropAccess(((Assignment) node).getLeft()); } break; // Property definitions are valid, if at the root of an expression. case com.

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>google.javascript.rhino.head.Token.GETPROP: case com.google.javascript.rhino.head.Token.GETELEM: valid = isExprStmt(node.getParent()); break; case com.google.javascript.rhino.head.Token.CALL: valid = info.isDefine(); break; } if (!valid) { errorReporter.warning(MISPLACED_TYPE_ANNOTATION, sourceName, node.getLineno(), "", 0); } } } private boolean isPropAccess(AstNode node) { return node.getType() == com.google.javascript.rhino.head.Token.GETPROP || node.getType() == com.google.javascript.rhino.head.Token.GETELEM; } private boolean isExprStmt(AstNode node) { return node.getType() == com.google.javascript.rhino.head.Token.EXPR_RESULT || node.getType() == com.google.javascript.rhino.head.Token.EXPR_VOID; } private Node transform(AstNode node) { Node irNode = justTransform(node); JSDocInfo jsDocInfo = handleJsDoc(node, irNode); if (jsDocInfo != null) { irNode = maybeInjectCastNode(node, jsDocInfo, irNode); irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, node); return irNode; } private Node maybeInjectCastNode(AstNode node, JSDocInfo info, Node irNode) { if (node.getType() == com.google.javascript.rhino.head.Token.LP && node instanceof ParenthesizedExpression && info.hasType()) { irNode = newNode(Token.CAST, irNode); } return irNode; } /** * Parameter NAMEs are special, because they can have inline type docs * attached. * * function f(/** string &#42;/ x) {} * annotates 'x' as a string. * * @see http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs */ private Node transformParameter(AstNode node) { Node irNode = justTransform(node); Comment comment = node.getJsDocNode(); if (comment != null) { JSDocInfo info = parseInlineTypeDoc(comment, irNode); if (info != null) { irNode.setJSDocInfo(info); } } setSourceInfo(irNode, node); return irNode; } private Node transformNameAsString(Name node) { Node irNode = transformDispatcher.processName(node, true); JSDocInfo jsDocInfo = handleJsDoc(node, irNode); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } setSource

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>AbsolutePosition(); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser parser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, position2charno(position) + numOpeningChars), node, irNode, config, errorReporter); return parser.parseInlineTypeDoc(); } // Set the length on the node if we're in IDE mode. private void maybeSetLengthFrom(Node node, AstNode source) { if (config.isIdeMode) { node.setLength(source.getLength()); } } private int position2charno(int position) { int lineIndex = sourceString.lastIndexOf('\n', position); if (lineIndex == -1) { return position; } else { // Subtract one for initial position being 0. return position - lineIndex - 1; } } private Node justTransform(AstNode node) { return transformDispatcher.process(node); } private class TransformDispatcher extends TypeSafeDispatcher<Node> { private Node processGeneric( com.google.javascript.rhino.head.Node n) { Node node = newNode(transformTokenType(n.getType())); for (com.google.javascript.rhino.head.Node child : n) { node.addChildToBack(transform((AstNode) child)); } return node; } /** * Transforms the given node and then sets its type to Token.STRING if it * was Token.NAME. If its type was already Token.STRING, then quotes it. * Used for properties, as the old AST uses String tokens, while the new one * uses Name tokens for unquoted strings. For example, in * var o = {'a' : 1, b: 2}; * the string 'a' is quoted, while the name b is turned into a string, but * unquoted. */ private Node transformAsString(AstNode n) { Node ret; if (n instanceof Name) { ret = transformNameAsString((Name) n); } else if (n instanceof NumberLiteral) { ret = transformNumberAsString((NumberLiteral) n); ret.putBooleanProp(Node.QUOTED_PROP, true); } else { ret = transform(n); ret.putBooleanProp(Node.QUOTED_PROP, true); } Preconditions.checkState(ret.isString()); return ret; } @Override Node processArrayLiteral(ArrayLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.ARRAYLIT); for (AstNode child : literalNode.getElements()) { Node c = transform(child); node.addChildToBack(c); } return node; } @Override Node processAssignment(Assignment

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> assignmentNode) { Node assign = processInfixExpression(assignmentNode); Node target = assign.getFirstChild(); if (!validAssignmentTarget(target)) { errorReporter.error( "invalid assignment target", sourceName, target.getLineno(), "", 0); } return assign; } @Override Node processAstRoot(AstRoot rootNode) { Node node = newNode(Token.SCRIPT); for (com.google.javascript.rhino.head.Node child : rootNode) { node.addChildToBack(transform((AstNode) child)); } parseDirectives(node); return node; } /** * Parse the directives, encode them in the AST, and remove their nodes. * * For information on ES5 directives, see section 14.1 of * ECMA-262, Edition 5. * * It would be nice if Rhino would eventually take care of this for * us, but right now their directive-processing is a one-off. */ private void parseDirectives(Node node) { // Remove all the directives, and encode them in the AST. Set<String> directives = null; while (isDirective(node.getFirstChild())) { String directive = node.removeFirstChild().getFirstChild().getString(); if (directives == null) { directives = Sets.newHashSet(directive); } else { directives.add(directive); } } if (directives != null) { node.setDirectives(directives); } } private boolean isDirective(Node n) { if (n == null) { return false; } int nType = n.getType(); return nType == Token.EXPR_RESULT && n.getFirstChild().isString() && ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString()); } @Override Node processBlock(Block blockNode) { return processGeneric(blockNode); } @Override Node processBreakStatement(BreakStatement statementNode) { Node node = newNode(Token.BREAK); if (statementNode.getBreakLabel() != null) { Node labelName = transform(statementNode.getBreakLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processCatchClause(CatchClause clauseNode) { AstNode catchVar = clauseNode.getVarName(); Node node = newNode(Token.CATCH, transform(catchVar)); if (clauseNode.getCatchCondition() != null) { errorReporter.error( "Catch clauses are not supported", sourceName, clauseNode.getCatchCondition().getLineno(), "", 0); } node.addChildToBack(transformBlock(clauseNode.getBody())); return node; } @Override Node processConditionalExpression(ConditionalExpression exprNode) { return newNode( Token

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.HOOK, transform(exprNode.getTestExpression()), transform(exprNode.getTrueExpression()), transform(exprNode.getFalseExpression())); } @Override Node processContinueStatement(ContinueStatement statementNode) { Node node = newNode(Token.CONTINUE); if (statementNode.getLabel() != null) { Node labelName = transform(statementNode.getLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processDoLoop(DoLoop loopNode) { return newNode( Token.DO, transformBlock(loopNode.getBody()), transform(loopNode.getCondition())); } @Override Node processElementGet(ElementGet getNode) { return newNode( Token.GETELEM, transform(getNode.getTarget()), transform(getNode.getElement())); } @Override Node processEmptyExpression(EmptyExpression exprNode) { Node node = newNode(Token.EMPTY); return node; } @Override Node processEmptyStatement(EmptyStatement exprNode) { Node node = newNode(Token.EMPTY); return node; } @Override Node processExpressionStatement(ExpressionStatement statementNode) { Node node = newNode(transformTokenType(statementNode.getType())); node.addChildToBack(transform(statementNode.getExpression())); return node; } @Override Node processForInLoop(ForInLoop loopNode) { if (loopNode.isForEach()) { errorReporter.error( "unsupported language extension: for each", sourceName, loopNode.getLineno(), "", 0); // Return the bare minimum to put the AST in a valid state. return newNode(Token.EXPR_RESULT, Node.newNumber(0)); } return newNode( Token.FOR, transform(loopNode.getIterator()), transform(loopNode.getIteratedObject()), transformBlock(loopNode.getBody())); } @Override Node processForLoop(ForLoop loopNode) { Node node = newNode( Token.FOR, transform(loopNode.getInitializer()), transform(loopNode.getCondition()), transform(loopNode.getIncrement())); node.addChildToBack(transformBlock(loopNode.getBody())); return node; } @Override Node processFunctionCall(FunctionCall callNode) { Node node = newNode(transformTokenType(callNode.getType()), transform(callNode.getTarget())); for (AstNode child : callNode.getArguments()) { node.addChildToBack(transform(child)); } node.setLineno(node.getFirstChild().getLineno()); node.setCharno(node.getFirstChild().getCharno()); maybeSetLengthFrom(node, callNode); return node; } @Override Node processFunctionNode(FunctionNode functionNode) { Name name = functionNode.getFunctionName(); Boolean isUnnamedFunction

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> private boolean isReservedKeyword(String identifier) { return reservedKeywords != null && reservedKeywords.contains(identifier); } @Override Node processNewExpression(NewExpression exprNode) { Node node = newNode( transformTokenType(exprNode.getType()), transform(exprNode.getTarget())); for (AstNode child : exprNode.getArguments()) { node.addChildToBack(transform(child)); } node.setLineno(exprNode.getLineno()); node.setCharno(position2charno(exprNode.getAbsolutePosition())); maybeSetLengthFrom(node, exprNode); return node; } @Override Node processNumberLiteral(NumberLiteral literalNode) { return newNumberNode(literalNode.getNumber()); } @Override Node processObjectLiteral(ObjectLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.OBJECTLIT); for (ObjectProperty el : literalNode.getElements()) { if (config.languageMode == LanguageMode.ECMASCRIPT3) { if (el.isGetter()) { reportGetter(el); continue; } else if (el.isSetter()) { reportSetter(el); continue; } } Node key = transformAsString(el.getLeft()); key.setType(Token.STRING_KEY); Node value = transform(el.getRight()); if (el.isGetter()) { key.setType(Token.GETTER_DEF); Preconditions.checkState(value.isFunction()); if (getFnParamNode(value).hasChildren()) { reportGetterParam(el.getLeft()); } } else if (el.isSetter()) { key.setType(Token.SETTER_DEF); Preconditions.checkState(value.isFunction()); if (!getFnParamNode(value).hasOneChild()) { reportSetterParam(el.getLeft()); } } key.addChildToFront(value); node.addChildToBack(key); } return node; } /** * @param fnNode The function. * @return The Node containing the Function parameters. */ Node getFnParamNode(Node fnNode) { // Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.isFunction()); return fnNode.getFirstChild().getNext(); } @Override Node processObjectProperty(ObjectProperty propertyNode) { return processInfixExpression(propertyNode); } @Override Node processParenthesizedExpression(ParenthesizedExpression exprNode) { Node node = transform(exprNode.getExpression()); return node; } @Override Node processPropertyGet(PropertyGet getNode) { Node leftChild = transform(getNode.getTarget()); Node newNode = newNode( Token.GETPROP, leftChild, transformAsString(getNode.getProperty())); newNode.setLineno(leftChild.get

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>, transform(expr)); } Node block = newNode(Token.BLOCK); block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); block.setLineno(caseNode.getLineno()); block.setCharno(position2charno(caseNode.getAbsolutePosition())); maybeSetLengthFrom(block, caseNode); if (caseNode.getStatements() != null) { for (AstNode child : caseNode.getStatements()) { block.addChildToBack(transform(child)); } } node.addChildToBack(block); return node; } @Override Node processSwitchStatement(SwitchStatement statementNode) { Node node = newNode(Token.SWITCH, transform(statementNode.getExpression())); for (AstNode child : statementNode.getCases()) { node.addChildToBack(transform(child)); } return node; } @Override Node processThrowStatement(ThrowStatement statementNode) { return newNode(Token.THROW, transform(statementNode.getExpression())); } @Override Node processTryStatement(TryStatement statementNode) { Node node = newNode(Token.TRY, transformBlock(statementNode.getTryBlock())); Node block = newNode(Token.BLOCK); boolean lineSet = false; for (CatchClause cc : statementNode.getCatchClauses()) { // Mark the enclosing block at the same line as the first catch // clause. if (lineSet == false) { block.setLineno(cc.getLineno()); maybeSetLengthFrom(block, cc); lineSet = true; } block.addChildToBack(transform(cc)); } node.addChildToBack(block); AstNode finallyBlock = statementNode.getFinallyBlock(); if (finallyBlock != null) { node.addChildToBack(transformBlock(finallyBlock)); } // If we didn't set the line on the catch clause, then // we've got an empty catch clause. Set its line to be the same // as the finally block (to match Old Rhino's behavior.) if ((lineSet == false) && (finallyBlock != null)) { block.setLineno(finallyBlock.getLineno()); maybeSetLengthFrom(block, finallyBlock); } return node; } @Override Node processUnaryExpression(UnaryExpression exprNode) { int type = transformTokenType(exprNode.getType()); Node operand = transform(exprNode.getOperand()); if (type == Token.NEG && operand.isNumber()) { operand.setDouble(-operand.getDouble()); return operand; } else { if (type == Token.DELPROP && !(operand.isGetProp() || operand.isGetElem() || operand.isName())) { String msg = "Invalid delete operand. Only properties can be deleted."; errorReporter.error( msg, sourceName, operand.getLineno(), "", 0

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>); } else if (type == Token.INC || type == Token.DEC) { if (!validAssignmentTarget(operand)) { String msg = (type == Token.INC) ? "invalid increment target" : "invalid decrement target"; errorReporter.error( msg, sourceName, operand.getLineno(), "", 0); } } Node node = newNode(type, operand); if (exprNode.isPostfix()) { node.putBooleanProp(Node.INCRDECR_PROP, true); } return node; } } private boolean validAssignmentTarget(Node target) { switch (target.getType()) { case Token.CAST: // CAST is a bit weird, but syntactically valid. case Token.NAME: case Token.GETPROP: case Token.GETELEM: return true; } return false; } @Override Node processVariableDeclaration(VariableDeclaration declarationNode) { if (!config.acceptConstKeyword && declarationNode.getType() == com.google.javascript.rhino.head.Token.CONST) { processIllegalToken(declarationNode); } Node node = newNode(Token.VAR); for (VariableInitializer child : declarationNode.getVariables()) { node.addChildToBack(transform(child)); } return node; } @Override Node processVariableInitializer(VariableInitializer initializerNode) { Node node = transform(initializerNode.getTarget()); if (initializerNode.getInitializer() != null) { Node initalizer = transform(initializerNode.getInitializer()); node.addChildToBack(initalizer); } return node; } @Override Node processWhileLoop(WhileLoop loopNode) { return newNode( Token.WHILE, transform(loopNode.getCondition()), transformBlock(loopNode.getBody())); } @Override Node processWithStatement(WithStatement statementNode) { return newNode( Token.WITH, transform(statementNode.getExpression()), transformBlock(statementNode.getStatement())); } @Override Node processIllegalToken(AstNode node) { errorReporter.error( "Unsupported syntax: " + com.google.javascript.rhino.head.Token.typeToName( node.getType()), sourceName, node.getLineno(), "", 0); return newNode(Token.EMPTY); } void reportDestructuringAssign(AstNode node) { errorReporter.error( "destructuring assignment forbidden", sourceName, node.getLineno(), "", 0); } void reportGetter(AstNode node) { errorReporter.error( GETTER_ERROR_MESSAGE, sourceName, node.getLineno(), "", 0); } void reportSetter(AstNode node) { errorReporter.error( SETTER_ERROR_MESSAGE, sourceName, node.getLineno(), "", 0); } void report

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { ReferenceCollectingCallback callback = new ReferenceCollectingCallback( compiler, new ReferenceCheckingBehavior()); callback.hotSwapScript(scriptRoot, originalRoot); } /** * Behavior that checks variables for redeclaration or early references * just after they go out of scope. */ private class ReferenceCheckingBehavior implements Behavior { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) { // TODO(bashir) In hot-swap version this means that for global scope we // only go through all global variables accessed in the modified file not // all global variables. This should be fixed. // Check all vars after finishing a scope for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); checkVar(v, referenceMap.getReferences(v).references); } } /** * If the variable is declared more than once in a basic block, generate a * warning. Also check if a variable is used in a given scope before it is * declared, which suggest a likely error. Relies on the fact that * references is in parse-tree order. */ private void checkVar(Var v, List<Reference> references) { blocksWithDeclarations.clear(); boolean isDeclaredInScope = false; boolean isUnhoistedNamedFunction = false; Reference hoistedFn = null; // Look for hoisted functions. for (Reference reference : references) { if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); boolean allowDupe = SyntacticScopeCreator.hasDuplicateDeclarationSuppression( reference.getNode(), v); if (isDeclaration && !allowDupe) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { // TODO(johnlenz): Fix AST generating clients that so they would // have property StaticSourceFile attached at each node. Or // better yet, make sure the generated code never violates // the requirement to pass aggressive var check! String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> (inputsById.containsKey(ast.getInputId())) { throw new IllegalArgumentException("Conflicting externs name: " + name); } CompilerInput input = new CompilerInput(ast, true); putCompilerInput(input.getInputId(), input); externsRoot.addChildToFront(ast.getAstRoot(this)); externs.add(0, input); return input; } private CompilerInput putCompilerInput(InputId id, CompilerInput input) { input.setCompiler(this); return inputsById.put(id, input); } /** Add a source input dynamically. Intended for incremental compilation. */ void addIncrementalSourceAst(JsAst ast) { InputId id = ast.getInputId(); Preconditions.checkState(getInput(id) == null, "Duplicate input %s", id.getIdName()); putCompilerInput(id, new CompilerInput(ast)); } /** * Replace a source input dynamically. Intended for incremental * re-compilation. * * If the new source input doesn't parse, then keep the old input * in the AST and return false. * * @return Whether the new AST was attached successfully. */ boolean replaceIncrementalSourceAst(JsAst ast) { CompilerInput oldInput = getInput(ast.getInputId()); Preconditions.checkNotNull(oldInput, "No input to replace: %s", ast.getInputId().getIdName()); Node newRoot = ast.getAstRoot(this); if (newRoot == null) { return false; } Node oldRoot = oldInput.getAstRoot(this); if (oldRoot != null) { oldRoot.getParent().replaceChild(oldRoot, newRoot); } else { getRoot().getLastChild().addChildToBack(newRoot); } CompilerInput newInput = new CompilerInput(ast); putCompilerInput(ast.getInputId(), newInput); JSModule module = oldInput.getModule(); if (module != null) { module.addAfter(newInput, oldInput); module.remove(oldInput); } // Verify the input id is set properly. Preconditions.checkState( newInput.getInputId().equals(oldInput.getInputId())); InputId inputIdOnAst = newInput.getAstRoot(this).getInputId(); Preconditions.checkState(newInput.getInputId().equals(inputIdOnAst)); inputs.remove(oldInput); return true; } /** * Add a new source input dynamically. Intended for incremental compilation. * <p> * If the new source input doesn't parse, it will not be added, and a false * will be returned. * * @param ast the JS Source to add. * @return true if the source was added successfully, false otherwise. * @throws IllegalStateException if an input for this ast already exists. */ boolean addNewSourceAst(JsAst ast) { CompilerInput oldInput = getInput(ast.getInputId

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached // from the AST. // Is it meaningful to build a scope for detached FUNCTION node? } final Node fnNameNode = n.getFirstChild(); final Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.isParamList()); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.isName()); declareVar(a); } // Body scanVars(body); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().isName()); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block); return; // only one child to scan case Token.SCRIPT: inputId = n.getInputId(); Preconditions.checkNotNull(inputId); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration(

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top-level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.isCatch() && parent.isCatch()) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar); if (!allowDupe) { compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. */ private void declareVar(Node n) { Preconditions.checkState(n.isName()); CompilerInput input = compiler.getInput(inputId); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } /** * @param n The name node to check. * @param origVar The associated Var. * @return Whether duplicated declarations warnings should be suppressed * for the given node. */ static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); Node origParent = origVar.getParentNode(); JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } if (info != null && info.getSuppressions().contains("duplicate")) { return true; } info = origVar.nameNode.getJSDocInfo(); if (info == null) { info = origParent.getJSDocInfo(); } return (info != null && info.getSuppressions().

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> "identifiers ending in '__' cannot be used in Caja"); static final DiagnosticType DUPLICATE_OBJECT_KEY = DiagnosticType.warning( "JSC_DUPLICATE_OBJECT_KEY", "object literals cannot contain duplicate keys in ES5 strict mode"); static final DiagnosticType BAD_FUNCTION_DECLARATION = DiagnosticType.error( "JSC_BAD_FUNCTION_DECLARATION", "functions can only be declared at top level or immediately within " + "another function in ES5 strict mode"); private final AbstractCompiler compiler; private final boolean noVarCheck; private final boolean noCajaChecks; StrictModeCheck(AbstractCompiler compiler) { this(compiler, false, false); } StrictModeCheck( AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) { this.compiler = compiler; this.noVarCheck = noVarCheck; this.noCajaChecks = noCajaChecks; } @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); NodeTraversal.traverse(compiler, root, new NonExternChecks()); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { checkFunctionUse(t, n); } else if (n.isName()) { if (!isDeclaration(n)) { checkNameUse(t, n); } } else if (n.isAssign()) { checkAssignment(t, n); } else if (n.isDelProp()) { checkDelete(t, n); } else if (n.isObjectLit()) { checkObjectLiteral(t, n); } else if (n.isLabel()) { checkLabel(t, n); } } /** Checks that the function is used legally. */ private void checkFunctionUse(NodeTraversal t, Node n) { if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { t.report(n, BAD_FUNCTION_DECLARATION); } } /** * Determines if the given name is a declaration, which can be a declaration * of a variable, function, or argument. */ private static boolean isDeclaration(Node n) { switch (n.getParent().getType()) { case Token.VAR: case Token.FUNCTION: case Token.CATCH: return true; case Token.PARAM_LIST: return n.getParent().getParent().isFunction(); default: return false; } } /** Checks that the given name is used legally. */ private void checkNameUse(NodeTraversal t, Node n) { Var v = t.getScope().getVar(n.getString()); if (v == null) { // In particular, this prevents creating a global variable by assigning // to it without a declaration. if (!no

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> msgNode != null && msgNode.isCall(); if (!isMessageName(messageKey, isNewStyleMessage)) { return; } if (msgNode == null) { compiler.report( traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey)); return; } // Just report a warning if a qualified messageKey that looks like a message // (e.g. "a.b.MSG_X") doesn't use goog.getMsg(). if (isNewStyleMessage) { googMsgNodes.remove(msgNode); } else if (style != JsMessage.Style.LEGACY) { compiler.report(traversal.makeError(node, checkLevel, MESSAGE_NOT_INITIALIZED_USING_NEW_SYNTAX)); } boolean isUnnamedMsg = isUnnamedMessageName(messageKey); Builder builder = new Builder( isUnnamedMsg ? null : messageKey); builder.setSourceName(traversal.getSourceName()); try { if (isVar) { extractMessageFromVariable(builder, node, parent, parent.getParent()); } else { extractMessageFromProperty(builder, node.getFirstChild(), node); } } catch (MalformedException ex) { compiler.report(traversal.makeError(ex.getNode(), MESSAGE_TREE_MALFORMED, ex.getMessage())); return; } JsMessage extractedMessage = builder.build(idGenerator); // If asked to check named internal messages. if (needToCheckDuplications && !isUnnamedMsg && !extractedMessage.isExternal()) { checkIfMessageDuplicated(messageKey, msgNode); } trackMessage(traversal, extractedMessage, messageKey, msgNode, isUnnamedMsg); if (extractedMessage.isEmpty()) { // value of the message is an empty string. Translators do not like it. compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_TEXT, messageKey)); } // New-style messages must have descriptions. We don't emit a warning // for legacy-style messages, because there are thousands of // them in legacy code that are not worth the effort to fix, since they've // already been translated anyway. String desc = extractedMessage.getDesc(); if (isNewStyleMessage && (desc == null || desc.trim().isEmpty()) && !extractedMessage.isExternal()) { compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_DESCRIPTION, messageKey)); } JsMessageDefinition msgDefinition = new JsMessageDefinition( node, msgNode, msgNodeParent); processJsMessage(extractedMessage, msgDefinition); } /** * Track a message for later retrieval. * * This is used for tracking duplicates, and for figuring out message * fallback. Not all message types are trackable, because that would * require a more sophisticated analysis. e.g., * function f(s) {

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>Child(); String desc = extractStringFromStringExprNode(valueNode); if (desc.startsWith(HIDDEN_DESC_PREFIX)) { builder.setDesc(desc.substring(HIDDEN_DESC_PREFIX.length()).trim()); builder.setIsHidden(true); } else { builder.setDesc(desc); } return true; } } return false; } /** * Initializes the meta data in a message builder given a node that may * contain JsDoc properties. * * @param builder the message builder whose meta data will be initialized * @param node the node with the message's JSDoc properties * @return true if message has JsDoc with valid description in @desc * annotation */ private boolean maybeInitMetaDataFromJsDoc(Builder builder, Node node) { boolean messageHasDesc = false; JSDocInfo info = node.getJSDocInfo(); if (info != null) { String desc = info.getDescription(); if (desc != null) { builder.setDesc(desc); messageHasDesc = true; } if (info.isHidden()) { builder.setIsHidden(true); } if (info.getMeaning() != null) { builder.setMeaning(info.getMeaning()); } } return messageHasDesc; } /** * Returns the string value associated with a node representing a JS string or * several JS strings added together (e.g. {@code 'str'} or {@code 's' + 't' + * 'r'}). * * @param node the node from where we extract the string * @return String representation of the node * @throws MalformedException if the parsed message is invalid */ private static String extractStringFromStringExprNode(Node node) throws MalformedException { switch (node.getType()) { case Token.STRING: return node.getString(); case Token.ADD: StringBuilder sb = new StringBuilder(); for (Node child : node.children()) { sb.append(extractStringFromStringExprNode(child)); } return sb.toString(); default: throw new MalformedException("STRING or ADD node expected; found: " + getReadableTokenName(node), node); } } /** * Initializes a message builder from a FUNCTION node. * <p> * <pre> * The tree should look something like: * * function * |-- name * |-- lp * | |-- name <arg1> * | -- name <arg2> * -- block * | * --return * | * --add * |-- string foo * -- name <arg1> * </pre> * * @param builder the message builder * @param node the function node that contains a message * @throws MalformedException if the parsed message is invalid */ private void extractFromFunctionNode(Builder builder

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>, Node node) throws MalformedException { Set<String> phNames = Sets.newHashSet(); for (Node fnChild : node.children()) { switch (fnChild.getType()) { case Token.NAME: // This is okay. The function has a name, but it is empty. break; case Token.PARAM_LIST: // Parse the placeholder names from the function argument list. for (Node argumentNode : fnChild.children()) { if (argumentNode.isName()) { String phName = argumentNode.getString(); if (phNames.contains(phName)) { throw new MalformedException("Duplicate placeholder name: " + phName, argumentNode); } else { phNames.add(phName); } } } break; case Token.BLOCK: // Build the message's value by examining the return statement Node returnNode = fnChild.getFirstChild(); if (!returnNode.isReturn()) { throw new MalformedException("RETURN node expected; found: " + getReadableTokenName(returnNode), returnNode); } for (Node child : returnNode.children()) { extractFromReturnDescendant(builder, child); } // Check that all placeholders from the message text have appropriate // object literal keys for (String phName : builder.getPlaceholders()) { if (!phNames.contains(phName)) { throw new MalformedException( "Unrecognized message placeholder referenced: " + phName, returnNode); } } break; default: throw new MalformedException( "NAME, LP, or BLOCK node expected; found: " + getReadableTokenName(node), fnChild); } } } /** * Appends value parts to the message builder by traversing the descendants * of the given RETURN node. * * @param builder the message builder * @param node the node from where we extract a message * @throws MalformedException if the parsed message is invalid */ private void extractFromReturnDescendant(Builder builder, Node node) throws MalformedException { switch (node.getType()) { case Token.STRING: builder.appendStringPart(node.getString()); break; case Token.NAME: builder.appendPlaceholderReference(node.getString()); break; case Token.ADD: for (Node child : node.children()) { extractFromReturnDescendant(builder, child); } break; default: throw new MalformedException( "STRING, NAME, or ADD node expected; found: " + getReadableTokenName(node), node); } } /** * Initializes a message builder from a CALL node. * <p> * The tree should look something like: * * <pre> * call * |-- getprop * | |-- name 'goog' * | +-- string 'getMsg' * | * |-- string

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> printErrorLocations(errors, jsType); if (!errors.isEmpty()) { suggestion = "Consider fixing errors for the following types:\n"; suggestion += Joiner.on("\n").join(errors); } } } compiler.report(JSError.make( t.getSourceName(), n, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), suggestion)); } } } /** * Processes a OBJECTLIT node. */ private void handleObjectLit(NodeTraversal t, Node n) { Node child = n.getFirstChild(); while (child != null) { // Maybe STRING, GET, SET // We should never see a mix of numbers and strings. String name = child.getString(); T type = typeSystem.getType(getScope(), n, name); Property prop = getProperty(name); if (!prop.scheduleRenaming(child, processProperty(t, prop, type, null))) { // TODO(user): It doesn't look like the user can do much in this // case right now. if (propertiesToErrorFor.containsKey(name)) { compiler.report(JSError.make( t.getSourceName(), child, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), "")); } } child = child.getNext(); } } private void printErrorLocations(List<String> errors, JSType t) { if (!t.isObject() || t.isAllType()) { return; } if (t.isUnionType()) { for (JSType alt : t.toMaybeUnionType().getAlternates()) { printErrorLocations(errors, alt); } return; } for (JSError error : invalidationMap.get(t)) { if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) { return; } errors.add( t.toString() + " at " + error.sourceName + ":" + error.lineNumber); } } /** * Processes a property, adding it to the list of properties to rename. * @return a representative type for the property reference, which will be * the highest type on the prototype chain of the provided type. In the * case of a union type, it will be the highest type on the prototype * chain of one of the members of the union. */ private T processProperty( NodeTraversal t, Property prop, T type, T relatedType) { type = typeSystem.restrictByNotNullOrUndefined(type); if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) { return null; } Iterable<T> alternatives = typeSystem.getTypeAlternatives(type); if (

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSTypeNative; import com.google.javascript.rhino.jstype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; import java.util.Set; /** * A code generator that outputs type annotations for functions and * constructors. */ class TypedCodeGenerator extends CodeGenerator { private final JSTypeRegistry registry; TypedCodeGenerator( CodeConsumer consumer, CompilerOptions options, JSTypeRegistry registry) { super(consumer, options); Preconditions.checkNotNull(registry); this.registry = registry; } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.isBlock() || parent.isScript())) { if (n.isFunction()) { add(getFunctionAnnotation(n)); } else if (n.isExprResult() && n.getFirstChild().isAssign()) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.isVar() && n.getFirstChild().getFirstChild() != null) { add(getTypeAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); } private String getTypeAnnotation(Node node) { // Only add annotations for things with JSDoc, or function literals. JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(node); if (jsdoc == null && !node.isFunction()) { return ""; } JSType type = node.getJSType(); if (type == null) { return ""; } else if (type.isFunctionType()) { return getFunctionAnnotation(node); } else if (type.

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> been deprecated: {2}"); static final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled( "JSC_DEPRECATED_CLASS", "Class {0} has been deprecated."); static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled( "JSC_DEPRECATED_CLASS_REASON", "Class {0} has been deprecated: {1}"); static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_GLOBAL_ACCESS", "Access to private variable {0} not allowed outside file {1}."); static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_PROPERTY_ACCESS", "Access to private property {0} of {1} not allowed here."); static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PROTECTED_PROPERTY_ACCESS", "Access to protected property {0} of {1} not allowed here."); static final DiagnosticType PRIVATE_OVERRIDE = DiagnosticType.disabled( "JSC_PRIVATE_OVERRIDE", "Overriding private property of {0}."); static final DiagnosticType EXTEND_FINAL_CLASS = DiagnosticType.error( "JSC_EXTEND_FINAL_CLASS", "{0} is not allowed to extend final class {1}."); static final DiagnosticType VISIBILITY_MISMATCH = DiagnosticType.disabled( "JSC_VISIBILITY_MISMATCH", "Overriding {0} property of {1} with {2} property."); static final DiagnosticType CONST_PROPERTY_REASSIGNED_VALUE = DiagnosticType.warning( "JSC_CONSTANT_PROPERTY_REASSIGNED_VALUE", "constant property {0} assigned a value more than once"); static final DiagnosticType CONST_PROPERTY_DELETED = DiagnosticType.warning( "JSC_CONSTANT_PROPERTY_DELETED", "constant property {0} cannot be deleted"); private final AbstractCompiler compiler; private final TypeValidator validator; // State about the current traversal. private int deprecatedDepth = 0; private int methodDepth = 0; private JSType currentClass = null; private final Multimap<String, String> initializedConstantProperties; CheckAccessControls(AbstractCompiler compiler) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.initializedConstantProperties = HashMultimap.create(); } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, this); } @Override public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n)) { deprecated

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE) { StaticSourceFile varSrc = var.getSourceFile(); StaticSourceFile refSrc = name.getStaticSourceFile(); if (varSrc != null && refSrc != null && !varSrc.getName().equals(refSrc.getName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } compiler.report( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), varSrc.getName())); } } } } } /** * Checks if a constructor is trying to override a final class. */ private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) { JSType type = fn.getJSType().toMaybeFunctionType(); if (type != null && type.isConstructor()) { JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent)); if (finalParentClass != null) { compiler.report( t.makeError(fn, EXTEND_FINAL_CLASS, type.getDisplayName(), finalParentClass.getDisplayName())); } } } /** * Determines whether the given property with @const tag got reassigned * @param t The current traversal. * @param getprop The getprop node. */ private void checkConstantProperty(NodeTraversal t, Node getprop) { // Check whether the property is modified Node parent = getprop.getParent(); boolean isDelete = parent.isDelProp(); if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop) && !parent.isInc() && !parent.isDec() && !isDelete) { return; } ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName); // Check whether constant properties are reassigned if (isConstant) { if (isDelete) { compiler.report( t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName)); return; } ObjectType oType = objectType; while (oType != null) { if (oType.hasReferenceName()) { if

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>isNew())) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.isGetProp() && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently OK to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.isAssign() && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated. */ private static boolean isDeprecatedFunction(Node n) { if (n.isFunction()) { JSType type = n.getJSType(); if (type != null) { return getTypeDeprecationInfo(type) != null; } } return false; } /** * Returns the deprecation reason for the type if it is marked * as being deprecated. Returns empty string if the type is deprecated * but no reason was given. Returns null if the type is not deprecated. */ private static String getTypeDeprecationInfo(JSType type) { if (type == null) { return null; } JSDocInfo info = type.getJSDocInfo(); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType objType = ObjectType.cast(type); if (objType != null) { ObjectType implicitProto = objType.getImplicitPrototype(); if (implicitProto != null) { return getTypeDeprecationInfo(implicitProto); } } return null; } /** * Returns if a property is declared constant. */ private static boolean isPropertyDeclaredConstant( ObjectType objectType, String prop) { for (; // Only objects with reference names can have constant properties. objectType != null && objectType.hasReferenceName(); objectType = objectType.getImplicitPrototype()) { JSDocInfo docInfo = objectType.getOwnPropertyJSDocInfo(prop); if (docInfo != null && docInfo.isConstant()) { return true;

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(getTypedScopeCreator()); TypeCheck check = makeTypeCheck(compiler); check.process(externs, root); compiler.getErrorManager().setTypedPercent(check.getTypedPercent()); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { makeTypeCheck(compiler).check(scriptRoot, false); } }; } }; /** * Checks possible execution paths of the program for problems: missing return * statements and dead code. */ final HotSwapPassFactory checkControlFlow = new HotSwapPassFactory("checkControlFlow", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { List<Callback> callbacks = Lists.newArrayList(); if (options.checkUnreachableCode.isOn()) { callbacks.add( new CheckUnreachableCode(compiler, options.checkUnreachableCode)); } if (options.checkMissingReturn.isOn() && options.checkTypes) { callbacks.add( new CheckMissingReturn(compiler, options.checkMissingReturn)); } return combineChecks(compiler, callbacks); } }; /** Checks access controls. Depends on type-inference. */ final HotSwapPassFactory checkAccessControls = new HotSwapPassFactory("checkAccessControls", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { return new CheckAccessControls(compiler); } }; /** Executes the given callbacks with a {@link CombinedCompilerPass}. */ private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler, List<Callback> callbacks) { Preconditions.checkArgument(callbacks.size() > 0); Callback[] array = callbacks.toArray(new Callback[callbacks.size()]); return new CombinedCompilerPass(compiler, array); } /** A compiler pass that resolves types in the global scope. */ class GlobalTypeResolver implements HotSwapCompilerPass { private final AbstractCompiler compiler; GlobalTypeResolver(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { if (topScope == null) { regenerateGlobalTypedScope(compiler, root.getParent()); } else { compiler.getTypeRegistry().resolveTypesInScope(topScope); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { patchGlobalTypedScope(compiler, scriptRoot); } } /** A compiler pass that clears the global scope. */ class ClearTypedScope implements CompilerPass { @Override public void process(Node externs, Node root) { clearTypedScope(); } } /** Checks global name usage. */ final PassFactory checkGlobalNames = new PassFactory("checkGlobalNames", true) { @Override protected CompilerPass create(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node extern

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>; // The node representing the "this" value, maybe null final Node thisValue; // The head of a Node list representing the parameters final Node parameters; public Bind(Node target, Node thisValue, Node parameters) { this.target = target; this.thisValue = thisValue; this.parameters = parameters; } /** * The number of parameters bound (not including the 'this' value). */ int getBoundParameterCount() { if (parameters == null) { return 0; } Node paramParent = parameters.getParent(); return paramParent.getChildCount() - paramParent.getIndexOfChild(parameters); } } /** * Whether this CALL function is testing for the existence of a property. */ public boolean isPropertyTestFunction(Node call); /** * Whether this GETPROP node is an alias for an object prototype. */ public boolean isPrototypeAlias(Node getProp); /** * Checks if the given method performs a object literal cast, and if it does, * returns information on the cast. By default, always returns null. Meant * to be overridden by subclasses. * * @param callNode A CALL node. */ public ObjectLiteralCast getObjectLiteralCast(Node callNode); /** * Gets a collection of all properties that are defined indirectly on global * objects. (For example, Closure defines superClass_ in the goog.inherits * call). */ public Collection<String> getIndirectlyDeclaredProperties(); /** * Returns the set of AssertionFunction. */ public Collection<AssertionFunctionSpec> getAssertionFunctions(); /** Specify the kind of inheritance */ static enum SubclassType { INHERITS, MIXIN } /** Record subclass relations */ static class SubclassRelationship { final SubclassType type; final String subclassName; final String superclassName; public SubclassRelationship(SubclassType type, Node subclassNode, Node superclassNode) { this.type = type; this.subclassName = subclassNode.getQualifiedName(); this.superclassName = superclassNode.getQualifiedName(); } } /** * Delegates provides a mechanism and structure for identifying where classes * can call out to optional code to augment their functionality. The optional * code is isolated from the base code through the use of a subclass in the * optional code derived from the delegate class in the base code. */ static class DelegateRelationship { /** The subclass in the base code. */ final String delegateBase; /** The class in the base code. */ final String delegator; DelegateRelationship(String delegateBase, String delegator) { this.delegateBase = delegateBase; this.delegator = delegator; } } /** * An object literal cast provides a mechanism to cast object literals to * other types without a warning. */ static class ObjectLiteralCast { /** Type to cast to. */ final String typeName; /** Object to cast. */ final Node objectNode

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> */ private static boolean definitionTypeContainsFunctionType(Definition def) { Node rhs = def.getRValue(); if (rhs == null) { return true; } switch (rhs.getType()) { case Token.ASSIGN: case Token.AND: case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.FUNCTION: case Token.HOOK: case Token.NAME: case Token.NEW: case Token.OR: return true; default: return false; } } /** * Get the value of the @nosideeffects annotation stored in the * doc info. */ private static boolean hasNoSideEffectsAnnotation(Node node) { JSDocInfo docInfo = node.getJSDocInfo(); return docInfo != null && docInfo.isNoSideEffects(); } /** * Gather function nodes that have @nosideeffects annotations. */ private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback { private final boolean inExterns; GatherNoSideEffectFunctions(boolean inExterns) { this.inExterns = inExterns; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (!inExterns && hasNoSideEffectsAnnotation(node)) { traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION); } if (node.isGetProp()) { if (parent.isExprResult() && hasNoSideEffectsAnnotation(node)) { noSideEffectFunctionNames.add(node); } } else if (node.isFunction()) { // The annotation may attached to the function node, the // variable declaration or assignment expression. boolean hasAnnotation = hasNoSideEffectsAnnotation(node); List<Node> nameNodes = Lists.newArrayList(); nameNodes.add(node.getFirstChild()); if (parent.isName()) { Node gramp = parent.getParent(); if (gramp.isVar() && gramp.hasOneChild() && hasNoSideEffectsAnnotation(gramp)) { hasAnnotation = true; } nameNodes.add(parent); } else if (parent.isAssign()) { if (hasNoSideEffectsAnnotation(parent)) { hasAnnotation = true; } nameNodes.add(parent.getFirstChild()); } if (hasAnnotation) { noSideEffectFunctionNames.addAll(nameNodes); } } } } /** * Set the no side effects property for CALL and NEW nodes that * refer to function names that are known to have no side effects. */ private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback { private final SimpleDefinitionFinder defFinder; SetNoSideEffectCallProperty(SimpleDefinitionFinder defFinder) { this.defFinder = defFinder; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) {

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>dropStub) { // Incomplete definition Definition definition = new ExternalNameOnlyDefinition(node); nameDefinitionMultimap.put(name, definition); definitionSiteMap.put(node, new DefinitionSite(node, definition, traversal.getModule(), traversal.inGlobalScope(), inExterns)); } } } } /** * @return Whether the node has a JSDoc that actually declares something. */ private boolean jsdocContainsDeclarations(Node node) { JSDocInfo info = node.getJSDocInfo(); return (info != null && info.containsDeclaration()); } } private class UseSiteGatheringCallback extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal traversal, Node node, Node parent) { Collection<Definition> defs = getDefinitionsReferencedAt(node); if (defs == null) { return; } Definition first = defs.iterator().next(); String name = getSimplifiedName(first.getLValue()); Preconditions.checkNotNull(name); nameUseSiteMultimap.put( name, new UseSite(node, traversal.getScope(), traversal.getModule())); } } /** * @param use A use site to check. * @return Whether the use is a call or new. */ static boolean isCallOrNewSite(UseSite use) { Node call = use.node.getParent(); if (call == null) { // The node has been removed from the AST. return false; } // We need to make sure we're dealing with a call to the function we're // optimizing. If the the first child of the parent is not the site, this // is a nested call and it's a call to another function. return NodeUtil.isCallOrNew(call) && call.getFirstChild() == use.node; } boolean canModifyDefinition(Definition definition) { if (isExported(definition)) { return false; } // Don't modify unused definitions for two reasons: // 1) It causes unnecessary churn // 2) Other definitions might be used to reflect on this one using // goog.reflect.object (the check for definitions with uses is below). Collection<UseSite> useSites = getUseSites(definition); if (useSites.isEmpty()) { return false; } for (UseSite site : useSites) { // This catches the case where an object literal in goog.reflect.object // and a prototype method have the same property name. // NOTE(nicksantos): Maps and trogedit both do this by different // mechanisms. Node nameNode = site.node; Collection<Definition> singleSiteDefinitions = getDefinitionsReferencedAt(nameNode); if (singleSiteDefinitions.size() > 1) { return false; } Preconditions.checkState(!singleSiteDefinitions.isEmpty()); Preconditions.checkState(singleSiteDefinitions.contains(definition)); }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> return true; } /** * @return Whether the definition is directly exported. */ private boolean isExported(Definition definition) { // Assume an exported method result is used. Node lValue = definition.getLValue(); if (lValue == null) { return true; } String partialName; if (lValue.isGetProp()) { partialName = lValue.getLastChild().getString(); } else if (lValue.isName()) { partialName = lValue.getString(); } else { // GETELEM is assumed to be an export or other expression are unknown // uses. return true; } CodingConvention codingConvention = compiler.getCodingConvention(); if (codingConvention.isExported(partialName)) { return true; } return false; } /** * @return Whether the function is defined in a non-aliasing expression. */ static boolean isSimpleFunctionDeclaration(Node fn) { Node parent = fn.getParent(); Node gramps = parent.getParent(); // Simple definition finder doesn't provide useful results in some // cases, specifically: // - functions with recursive definitions // - functions defined in object literals // - functions defined in array literals // Here we defined a set of known function declaration that are 'ok'. // Some projects seem to actually define "JSCompiler_renameProperty" // rather than simply having an extern definition. Don't mess with it. Node nameNode = SimpleDefinitionFinder.getNameNodeFromFunctionNode(fn); if (nameNode != null && nameNode.isName()) { String name = nameNode.getString(); if (name.equals(NodeUtil.JSC_PROPERTY_NAME_FN) || name.equals( ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) { return false; } } // example: function a(){}; if (NodeUtil.isFunctionDeclaration(fn)) { return true; } // example: a = function(){}; // example: var a = function(){}; if (fn.getFirstChild().getString().isEmpty() && (NodeUtil.isExprAssign(gramps) || parent.isName())) { return true; } return false; } /** * @return the node defining the name for this function (if any). */ static Node getNameNodeFromFunctionNode(Node function) { Preconditions.checkState(function.isFunction()); if (NodeUtil.isFunctionDeclaration(function)) { return function.getFirstChild(); } else { Node parent = function.getParent(); if (NodeUtil.isVarDeclaration(parent)) { return parent; } else if (parent.isAssign()) { return parent.getFirstChild(); } else if (NodeUtil.isObjectLitKey(parent)) { return parent; } } return null; } /** * Traverse a node and its children and remove any references to from * the structures.

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> */ void removeReferences(Node node) { if (DefinitionsRemover.isDefinitionNode(node)) { DefinitionSite defSite = definitionSiteMap.get(node); if (defSite != null) { Definition def = defSite.definition; String name = getSimplifiedName(def.getLValue()); if (name != null) { this.definitionSiteMap.remove(node); this.nameDefinitionMultimap.remove(name, node); } } } else { Node useSite = node; if (useSite.isGetProp()) { String propName = useSite.getLastChild().getString(); if (propName.equals("apply") || propName.equals("call")) { useSite = useSite.getFirstChild(); } } String name = getSimplifiedName(useSite); if (name != null) { this.nameUseSiteMultimap.remove(name, new UseSite(useSite, null, null)); } } for (Node child : node.children()) { removeReferences(child); } } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>. if (!block.hasChildren() && block.wasEmptyNode()) { t.getCompiler().report( t.makeError(block, SUSPICIOUS_SEMICOLON)); } } private void checkNaN(NodeTraversal t, Node n) { switch (n.getType()) { case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LT: case Token.NE: case Token.SHEQ: case Token.SHNE: reportIfNaN(t, n.getFirstChild()); reportIfNaN(t, n.getLastChild()); } } private void reportIfNaN(NodeTraversal t, Node n) { if (NodeUtil.isNaN(n)) { t.getCompiler().report( t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN)); } } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> if (provide != null) { provides.add(provide); } return; } else if (parent != null && !parent.isExprResult() && !parent.isScript()) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } } } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { return getSourceFile().getLine(lineNumber); } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public Region getRegion(int lineNumber) { return getSourceFile().getRegion(lineNumber); } public String getCode() throws IOException { return getSourceFile().getCode(); } /** Returns the module to which the input belongs. */ public JSModule getModule() { return module; } /** Sets the module to which the input belongs. */ public void setModule(JSModule module) { // An input may only belong to one module. Preconditions.checkArgument( module == null || this.module == null || this.module == module); this.module = module; } /** Overrides the module to which the input belongs. */ void overrideModule(JSModule module) { this.module = module; } public boolean isExtern() { if (ast == null || ast.getSourceFile() == null) { return false; } return ast.getSourceFile().isExtern(); } void setIsExtern(boolean isExtern) { if (ast == null || ast.getSourceFile() == null) { return; } ast.getSourceFile().setIsExtern(isExtern); } public int getLineOffset(int lineno) { return ast.getSourceFile().getLineOffset(lineno); } /** @return The number of lines in this input. */ public int getNumLines() { return ast.getSourceFile().getNumLines(); } @Override public String toString() { return getName(); } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { private static final long serialVersionUID = 1L; /** Warnings guards for each suppressible warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressible groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressibleGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { boolean visitedFunction = false; for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; if (type == Token.FUNCTION) { info = NodeUtil.getBestJSDocInfo(current); visitedFunction = true; } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } else if (current.isVar() || current.isAssign()) { // There's one edge case we're worried about: // if the warning points to an assigment to a function, we // want the suppressions on that function to apply. // It's OK if we double-count some cases. Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild()); if (rhs != null) { if (rhs.isCast()) { rhs = rhs.getFirstChild(); } if (rhs.isFunction() && !visitedFunction) {

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> case Token.OBJECTLIT: if (!mayHaveSideEffects(n)) { return TernaryValue.TRUE; } break; } return TernaryValue.UNKNOWN; } /** * Gets the value of a node as a String, or null if it cannot be converted. * When it returns a non-null String, this method effectively emulates the * <code>String()</code> JavaScript cast function. */ static String getStringValue(Node n) { // TODO(user): regex literals as well. switch (n.getType()) { case Token.STRING: case Token.STRING_KEY: return n.getString(); case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name)) { return name; } break; case Token.NUMBER: return getStringValue(n.getDouble()); case Token.FALSE: return "false"; case Token.TRUE: return "true"; case Token.NULL: return "null"; case Token.VOID: return "undefined"; case Token.NOT: TernaryValue child = getPureBooleanValue(n.getFirstChild()); if (child != TernaryValue.UNKNOWN) { return child.toBoolean(true) ? "false" : "true"; // reversed. } break; case Token.ARRAYLIT: return arrayToString(n); case Token.OBJECTLIT: return "[object Object]"; } return null; } static String getStringValue(double value) { long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString(value); } } /** * When converting arrays to string using Array.prototype.toString or * Array.prototype.join, the rules for conversion to String are different * than converting each element individually. Specifically, "null" and * "undefined" are converted to an empty string. * @param n A node that is a member of an Array. * @return The string representation. */ static String getArrayElementStringValue(Node n) { return (NodeUtil.isNullOrUndefined(n) || n.isEmpty()) ? "" : getStringValue(n); } static String arrayToString(Node literal) { Node first = literal.getFirstChild(); StringBuilder result = new StringBuilder(); for (Node n = first; n != null; n = n.getNext()) { String childValue = getArrayElementStringValue(n); if (childValue == null) { return null; } if (n != first) { result.append(','); } result.append(childValue); } return result.toString(); } /** * Gets the value of a node as a Number, or null if it cannot be converted.

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> * When it returns a non-null Double, this method effectively emulates the * <code>Number()</code> JavaScript cast function. */ static Double getNumberValue(Node n) { switch (n.getType()) { case Token.TRUE: return 1.0; case Token.FALSE: case Token.NULL: return 0.0; case Token.NUMBER: return n.getDouble(); case Token.VOID: if (mayHaveSideEffects(n.getFirstChild())) { return null; } else { return Double.NaN; } case Token.NAME: // Check for known constants String name = n.getString(); if (name.equals("undefined")) { return Double.NaN; } if (name.equals("NaN")) { return Double.NaN; } if (name.equals("Infinity")) { return Double.POSITIVE_INFINITY; } return null; case Token.NEG: if (n.getChildCount() == 1 && n.getFirstChild().isName() && n.getFirstChild().getString().equals("Infinity")) { return Double.NEGATIVE_INFINITY; } return null; case Token.NOT: TernaryValue child = getPureBooleanValue(n.getFirstChild()); if (child != TernaryValue.UNKNOWN) { return child.toBoolean(true) ? 0.0 : 1.0; // reversed. } break; case Token.STRING: return getStringNumberValue(n.getString()); case Token.ARRAYLIT: case Token.OBJECTLIT: String value = getStringValue(n); return value != null ? getStringNumberValue(value) : null; } return null; } static Double getStringNumberValue(String rawJsString) { if (rawJsString.contains("\u000b")) { // vertical tab is not always whitespace return null; } String s = trimJsWhiteSpace(rawJsString); // return ScriptRuntime.toNumber(s); if (s.length() == 0) { return 0.0; } if (s.length() > 2 && s.charAt(0) == '0' && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) { // Attempt to convert hex numbers. try { return Double.valueOf(Integer.parseInt(s.substring(2), 16)); } catch (NumberFormatException e) { return Double.NaN; } } if (s.length() > 3 && (s.charAt(0) == '-' || s.charAt(0) == '+') && s.charAt(1) == '0' && (s.charAt(2) == 'x' || s.charAt(2) == 'X')) { // hex numbers with explicit signs vary between browsers. return null; } // Firefox and IE treat the "Infinity

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>" differently. Firefox is case // insensitive, but IE treats "infinity" as NaN. So leave it alone. if (s.equals("infinity") || s.equals("-infinity") || s.equals("+infinity")) { return null; } try { return Double.parseDouble(s); } catch (NumberFormatException e) { return Double.NaN; } } static String trimJsWhiteSpace(String s) { int start = 0; int end = s.length(); while (end > 0 && isStrWhiteSpaceChar(s.charAt(end - 1)) == TernaryValue.TRUE) { end--; } while (start < end && isStrWhiteSpaceChar(s.charAt(start)) == TernaryValue.TRUE) { start++; } return s.substring(start, end); } /** * Copied from Rhino's ScriptRuntime */ public static TernaryValue isStrWhiteSpaceChar(int c) { switch (c) { case '\u000B': // <VT> return TernaryValue.UNKNOWN; // IE says "no", ECMAScript says "yes" case ' ': // <SP> case '\n': // <LF> case '\r': // <CR> case '\t': // <TAB> case '\u00A0': // <NBSP> case '\u000C': // <FF> case '\u2028': // <LS> case '\u2029': // <PS> case '\uFEFF': // <BOM> return TernaryValue.TRUE; default: return (Character.getType(c) == Character.SPACE_SEPARATOR) ? TernaryValue.TRUE : TernaryValue.FALSE; } } /** * Gets the function's name. This method recognizes five forms: * <ul> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * In two last cases with named function expressions, the second name is * returned (the variable of qualified name). * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getFunctionName(Node n) { Preconditions.checkState(n.isFunction()); Node parent = n.getParent(); switch (parent.getType()) { case Token.NAME: // var name = function() ... // var name2 = function name1() ... return parent.getQualifiedName(); case Token.ASSIGN: // qualified.name = function() ...

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> // qualified.name2 = function name1() ... return parent.getFirstChild().getQualifiedName(); default: // function name() ... String name = n.getFirstChild().getQualifiedName(); return name; } } /** * Gets the function's name. This method recognizes the forms: * <ul> * <li>{@code &#123;'name': function() ...&#125;}</li> * <li>{@code &#123;name: function() ...&#125;}</li> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ public static String getNearestFunctionName(Node n) { if (!n.isFunction()) { return null; } String name = getFunctionName(n); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.SETTER_DEF: case Token.GETTER_DEF: case Token.STRING_KEY: // Return the name of the literal's key. return parent.getString(); case Token.NUMBER: return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.CAST: case Token.NOT: return isImmutableValue(n.getFirstChild()); case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if the operator on this node is symmetric */ static boolean isSymmetricOperation(Node n) { switch (n.getType()) { case Token.EQ: // equal case Token.NE: // not equal case Token.SHEQ: // exactly equal case Token.SHNE: // exactly not equal case Token.MUL: // multiply, unlike add it only

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> works on numbers // or results NaN if any of the operators is not a number return true; } return false; } /** * Returns true if the operator on this node is relational. * the returned set does not include the equalities. */ static boolean isRelationalOperation(Node n) { switch (n.getType()) { case Token.GT: // equal case Token.GE: // not equal case Token.LT: // exactly equal case Token.LE: // exactly not equal return true; } return false; } /** * Returns the inverse of an operator if it is invertible. * ex. '>' ==> '<' */ static int getInverseOperator(int type) { switch (type) { case Token.GT: return Token.LT; case Token.LT: return Token.GT; case Token.GE: return Token.LE; case Token.LE: return Token.GE; } return Token.ERROR; } /** * Returns true if this is a literal value. We define a literal value * as any node that evaluates to the same thing regardless of when or * where it is evaluated. So /xyz/ and [3, 5] are literals, but * the name a is not. * * Function literals do not meet this definition, because they * lexically capture variables. For example, if you have * <code> * function() { return a; } * </code> * If it is evaluated in a different scope, then it * captures a different variable. Even if the function did not read * any captured variables directly, it would still fail this definition, * because it affects the lifecycle of variables in the enclosing scope. * * However, a function literal with respect to a particular scope is * a literal. * * @param includeFunctions If true, all function expressions will be * treated as literals. */ static boolean isLiteralValue(Node n, boolean includeFunctions) { switch (n.getType()) { case Token.CAST: return isLiteralValue(n.getFirstChild(), includeFunctions); case Token.ARRAYLIT: for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.REGEXP: // Return true only if all children are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>GT: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.NOT: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.TYPEOF: case Token.VOID: case Token.POS: case Token.NEG: case Token.URSH: return true; default: return false; } } /** * Creates an EXPR_RESULT. * * @param child The expression itself. * @return Newly created EXPR node with the child as subexpression. */ static Node newExpr(Node child) { return IR.exprResult(child).srcref(child); } /** * Returns true if the node may create new mutable state, or change existing * state. * * @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a> */ static boolean mayEffectMutableState(Node n) { return mayEffectMutableState(n, null); } static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, true, compiler); } /** * Returns true if the node which may have side effects when executed. */ static boolean mayHaveSideEffects(Node n) { return mayHaveSideEffects(n, null); } static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, false, compiler); } /** * Returns true if some node in n's subtree changes application state. * If {@code checkForNewObjects} is true, we assume that newly created * mutable objects (like object literals) change state. Otherwise, we assume * that they have no side effects. */ private static boolean checkForStateChangeHelper( Node n, boolean checkForNewObjects, AbstractCompiler compiler) { // Rather than id which ops may have side effects, id the ones // that we know to be safe switch (n.getType()) { // other side-effect free statements and expressions case Token.CAST: case Token.AND: case Token.BLOCK: case Token.EXPR_RESULT: case Token.HOOK: case Token.IF: case Token.IN: case Token.PARAM_LIST: case Token.NUMBER: case Token.OR: case Token.THIS: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: case Token.STRING_KEY: case Token.SWITCH: case Token.TRY: case Token.EMPTY: break; // Throws are by definition side effects case Token.THROW: return true; case Token.OBJECTLIT: if (checkForNewObjects) { return true

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> valid parse tree. return n.isName() && n.getParent().isVar(); } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); if (parent.isVar()) { return n.getFirstChild(); } else if (parent.isAssign() && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.isExprResult() && n.getFirstChild().isAssign(); } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.isExprResult() && n.getFirstChild().isCall(); } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.isFor() && n.getChildCount() == 3; } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (parent.isFunction()) { break; } } return false; } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) {

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT_CASE: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WHILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT_CASE: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously a node */ static Node getConditionExpression(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n); case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.isScript() || n.isBlock(); } /** * @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { return isStatementParent(n.getParent()); } static boolean isStatementParent(Node parent) { // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node can be // either part of an expression or a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.isCase() || n.isDefaultCase(); } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return n.isName() && !n.getString().isEmpty(); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.isTry() && parent.getChildCount() == 3 && child == parent.getLastChild(); } /** Whether the node is a CATCH container BLOCK. */ static boolean isTryCatchNodeContainer(Node n) { Node parent = n.getParent(); return parent.isTry() && parent.getFirstChild().getNext() == n; } /** Safely remove children while maintaining a valid node structure. */ static void removeChild(Node parent, Node node) { if (isTryFinallyNode(parent, node)) { if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) { // A finally can only be removed if there is a catch. parent.removeChild(node); } else { // Otherwise, only its children can be removed. node.detachChildren(); } } else if (node.isCatch()) { // The CATCH can can only be removed if there is a finally clause. Node tryNode = node.getParent().getParent(); Preconditions.checkState(NodeUtil.hasFinally(tryNode)); node.detachFromParent(); } else if (isTryCatchNodeContainer(node)) { // The container node itself can't be removed, but the contained CATCH // can if there is a 'finally' clause Node tryNode = node.getParent(); Preconditions.checkState(NodeUtil.hasFinally(tryNode)); node.detachChildren(); } else if (node.isBlock()) { // Simply empty the block. This maintains source location and // "synthetic"-ness. node.detachChildren(); } else if (isStatementBlock(parent) || isSwitchCase(node)) { // A statement in a block can simply be removed. parent.removeChild(node); } else if (parent.isVar()) { if (parent.hasMoreThanOneChild()) { parent.removeChild(node); } else { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // This would leave an empty VAR, remove the VAR itself. removeChild(parent.getParent(), parent); } } else if (parent.isLabel() && node == parent.getLastChild()) { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // A LABEL without children

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> can not be referred to, remove it. removeChild(parent.getParent(), parent); } else if (parent.isFor() && parent.getChildCount() == 4) { // Only Token.FOR can have an Token.EMPTY other control structure // need something for the condition. Others need to be replaced // or the structure removed. parent.replaceChild(node, IR.empty()); } else { throw new IllegalStateException("Invalid attempt to remove node: " + node.toString() + " of " + parent.toString()); } } /** * Add a finally block if one does not exist. */ static void maybeAddFinally(Node tryNode) { Preconditions.checkState(tryNode.isTry()); if (!NodeUtil.hasFinally(tryNode)) { tryNode.addChildrenToBack(IR.block().srcref(tryNode)); } } /** * Merge a block with its parent block. * @return Whether the block was removed. */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.isBlock()); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else { return false; } } /** * @param node A node * @return Whether the call is a NEW or CALL node. */ static boolean isCallOrNew(Node node) { return node.isCall() || node.isNew(); } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(fn.isFunction()); return fn.getLastChild(); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not part of a expression; see {@link #isFunctionExpression}). */ static boolean isFunctionDeclaration(Node n) { return n.isFunction() && isStatement(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration}). */ static boolean isHoistedFunctionDeclaration(Node n) { return isFunctionDeclaration(n) && (n.getParent().isScript() || n.getParent().getParent().isFunction()); } /** * Is a FUNCTION node an function expression? An function expression is one * that has either no name or a name that is not added to the current scope. * * <p>Some

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> examples of function expressions: * <pre> * (function () {}) * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;) {} * </pre> * * <p>Some examples of functions that are <em>not</em> expressions: * <pre> * function f() {} * if (x); else function f() {} * for (;;) { function f() {} } * </pre> * * @param n A node * @return Whether n is an function used within an expression. */ static boolean isFunctionExpression(Node n) { return n.isFunction() && !isStatement(n); } /** * Returns whether this is a bleeding function (an anonymous named function * that bleeds into the inner scope). */ static boolean isBleedingFunctionName(Node n) { return n.isName() && !n.getString().isEmpty() && isFunctionExpression(n.getParent()); } /** * Determines if a node is a function expression that has an empty body. * * @param node a node * @return whether the given node is a function expression that is empty */ static boolean isEmptyFunctionExpression(Node node) { return isFunctionExpression(node) && isEmptyBlock(node.getLastChild()); } /** * Determines if a function takes a variable number of arguments by * looking for references to the "arguments" var_args object. */ static boolean isVarArgsFunction(Node function) { // TODO(johnlenz): rename this function Preconditions.checkArgument(function.isFunction()); return isNameReferenced( function.getLastChild(), "arguments", MATCH_NOT_FUNCTION); } /** * @return Whether node is a call to methodName. * a.f(...) * a['f'](...) */ static boolean isObjectCallMethod(Node callNode, String methodName) { if (callNode.isCall()) { Node functionIndentifyingExpression = callNode.getFirstChild(); if (isGet(functionIndentifyingExpression)) { Node last = functionIndentifyingExpression.getLastChild(); if (last != null && last.isString()) { String propName = last.getString(); return (propName.equals(methodName)); } } } return false; } /** * @return Whether the callNode represents an expression in the form of: * x.call(...) * x['call'](...) */ static boolean isFunctionObjectCall(Node callNode) { return isObjectCallMethod(callNode, "call"); } /** * @return Whether the callNode represents an expression in the form of: * x.apply(...) * x['apply'](...) */ static boolean isFunctionObjectApply(Node callNode) { return isObjectCallMethod(callNode, "apply"); } /**

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> * Determines whether this node is strictly on the left hand side of an assign * or var initialization. Notably, this does not include all L-values, only * statements where the node is used only as an L-value. * * @param n The node * @param parent Parent of the node * @return True if n is the left hand of an assign */ static boolean isVarOrSimpleAssignLhs(Node n, Node parent) { return (parent.isAssign() && parent.getFirstChild() == n) || parent.isVar(); } /** * Determines whether this node is used as an L-value. Notice that sometimes * names are used as both L-values and R-values. * * We treat "var x;" as a pseudo-L-value, which kind of makes sense if you * treat it as "assignment to 'undefined' at the top of the scope". But if * we're honest with ourselves, it doesn't make sense, and we only do this * because it makes sense to treat this as syntactically similar to * "var x = 0;". * * @param n The node * @return True if n is an L-value. */ public static boolean isLValue(Node n) { Preconditions.checkArgument(n.isName() || n.isGetProp() || n.isGetElem()); Node parent = n.getParent(); if (parent == null) { return false; } return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) || (NodeUtil.isForIn(parent) && parent.getFirstChild() == n) || parent.isVar() || (parent.isFunction() && parent.getFirstChild() == n) || parent.isDec() || parent.isInc() || parent.isParamList() || parent.isCatch(); } /** * Determines whether a node represents an object literal key * (e.g. key1 in {key1: value1, key2: value2}). * * @param node A node */ static boolean isObjectLitKey(Node node) { switch (node.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return true; } return false; } /** * Get the name of an object literal key. * * @param key A node */ static String getObjectLitKeyName(Node key) { switch (key.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return key.getString(); } throw new IllegalStateException("Unexpected node type: " + key); } /** * @param key A OBJECTLIT key node. * @return The type expected when using the key. */ static JSType getObjectLitKeyTypeFromValueType(Node key,

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.isBlock() || addingRoot.isScript()); Preconditions.checkState(addingRoot.getFirstChild() == null || !addingRoot.getFirstChild().isScript()); return addingRoot; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @return A NAME or GETPROP node */ public static Node newQualifiedNameNode( CodingConvention convention, String name) { int endPos = name.indexOf('.'); if (endPos == -1) { return newName(convention, name); } Node node; String nodeName = name.substring(0, endPos); if ("this".equals(nodeName)) { node = IR.thisNode(); } else { node = newName(convention, nodeName); } int startPos; do { startPos = endPos + 1; endPos = name.indexOf('.', startPos); String part = (endPos == -1 ? name.substring(startPos) : name.substring(startPos, endPos)); Node propNode = IR.string(part); if (convention.isConstantKey(part)) { propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } node = IR.getprop(node, propNode); } while (endPos != -1); return node; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @return A NAME or GETPROP node */ public static Node newQualifiedNameNodeDeclaration( CodingConvention convention, String name, Node value, JSDocInfo info) { Node result; Node nameNode = newQualifiedNameNode(convention, name); if (nameNode.isName()) { result = IR.var(nameNode, value); result.setJSDocInfo(info); } else { result = IR.exprResult(IR.assign(nameNode, value)); result.getFirstChild().setJSDocInfo(info); } return result; } /** * Creates a node representing a qualified name, copying over the source * location information from the basis node and assigning the given original * name to the node. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @param basis

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>NAME_PROP, originalName); return nameNode; } /** Test if all characters in the string are in the Basic Latin (aka ASCII) * character set - that they have UTF-16 values equal to or below 0x7f. * This check can find which identifiers with Unicode characters need to be * escaped in order to allow resulting files to be processed by non-Unicode * aware UNIX tools and editors. * * * See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode * for more on Basic Latin. * * @param s The string to be checked for ASCII-goodness. * * @return True if all characters in the string are in Basic Latin set. */ static boolean isLatin(String s) { int len = s.length(); for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c > LARGEST_BASIC_LATIN) { return false; } } return true; } /** * Determines whether the given name is a valid variable name. */ static boolean isValidSimpleName(String name) { return TokenStream.isJSIdentifier(name) && !TokenStream.isKeyword(name) && // no Unicode escaped characters - some browsers are less tolerant // of Unicode characters that might be valid according to the // language spec. // Note that by this point, Unicode escapes have been converted // to UTF-16 characters, so we're only searching for character // values, not escapes. isLatin(name); } /** * Determines whether the given name is a valid qualified name. */ // TODO(nicksantos): This should be moved into a "Language" API, // so that the results are different for es5 and es3. public static boolean isValidQualifiedName(String name) { if (name.endsWith(".") || name.startsWith(".")) { return false; } String[] parts = name.split("\\."); for (String part : parts) { if (!isValidSimpleName(part)) { return false; } } return true; } /** * Determines whether the given name can appear on the right side of * the dot operator. Many properties (like reserved words) cannot. */ static boolean isValidPropertyName(String name) { return isValidSimpleName(name); } private static class VarCollector implements Visitor { final Map<String, Node> vars = Maps.newLinkedHashMap(); @Override public void visit(Node n) { if (n.isName()) { Node parent = n.getParent(); if (parent != null && parent.isVar()) { String name = n.getString(); if (!vars.containsKey(name)) { vars.put(name, n); } } } } } /** * Retrieves vars declared in the current node tree, excluding descent scopes

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> @Override public boolean apply(Node n) { return n.getType() == type; } } /** * A predicate for matching var or function declarations. */ static class MatchDeclaration implements Predicate<Node> { @Override public boolean apply(Node n) { return isFunctionDeclaration(n) || n.isVar(); } } /** * A predicate for matching anything except function nodes. */ private static class MatchNotFunction implements Predicate<Node>{ @Override public boolean apply(Node n) { return !n.isFunction(); } } static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction(); /** * A predicate for matching statements without exiting the current scope. */ static class MatchShallowStatement implements Predicate<Node>{ @Override public boolean apply(Node n) { Node parent = n.getParent(); return n.isBlock() || (!n.isFunction() && (parent == null || isControlStructure(parent) || isStatementBlock(parent))); } } /** * Finds the number of times a type is referenced within the node tree. */ static int getNodeTypeReferenceCount( Node node, int type, Predicate<Node> traverseChildrenPred) { return getCount(node, new MatchNodeType(type), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNameNode(name), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name) { return isNameReferenced(node, name, Predicates.<Node>alwaysTrue()); } /** * Finds the number of times a simple name is referenced within the node tree. */ static int getNameReferenceCount(Node node, String name) { return getCount( node, new MatchNameNode(name), Predicates.<Node>alwaysTrue()); } /** * @return Whether the predicate is true for the node or any of its children. */ static boolean has(Node node, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { if (pred.apply(node)) { return true; } if (!traverseChildrenPred.apply(node)) { return false; } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { if (has(c, pred, traverseChildrenPred)) { return true; } } return false; } /** * @return The number of times the the predicate is true for the node * or any of its children. */ static int getCount( Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { int total = 0; if (pred.

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> node represents a constant variable */ static boolean isConstantName(Node node) { return node.getBooleanProp(Node.IS_CONSTANT_NAME); } /** Whether the given name is constant by coding convention. */ static boolean isConstantByConvention( CodingConvention convention, Node node, Node parent) { if (parent.isGetProp() && node == parent.getLastChild()) { return convention.isConstantKey(node.getString()); } else if (isObjectLitKey(node)) { return convention.isConstantKey(node.getString()); } else if (node.isName()) { return convention.isConstant(node.getString()); } return false; } /** * Get the JSDocInfo for a function. */ public static JSDocInfo getFunctionJSDocInfo(Node n) { Preconditions.checkState(n.isFunction()); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.isAssign()) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.isName()) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = n.getSourceFileName(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static StaticSourceFile getSourceFile(Node n) { StaticSourceFile sourceName = null; while (sourceName == null && n != null) { sourceName = n.getStaticSourceFile(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The InputId property on the node or its ancestors. */ public static InputId getInputId(Node n) { while (n != null && !n.isScript()) { n = n.getParent(); } return (n != null && n.isScript()) ? n.getInputId() : null; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = !isGet(callTarget); Node call = IR.call(callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> } throw new IllegalStateException( "Unexpected expression node" + value + "\n parent:" + value.getParent()); } } /** * Given the first sibling, this returns the nth * sibling or null if no such sibling exists. * This is like "getChildAtIndex" but returns null for non-existent indexes. */ private static Node getNthSibling(Node first, int index) { Node sibling = first; while (index != 0 && sibling != null) { sibling = sibling.getNext(); index--; } return sibling; } /** * Given the function, this returns the nth * argument or null if no such parameter exists. */ static Node getArgumentForFunction(Node function, int index) { Preconditions.checkState(function.isFunction()); return getNthSibling( function.getFirstChild().getNext().getFirstChild(), index); } /** * Given the new or call, this returns the nth * argument of the call or null if no such argument exists. */ static Node getArgumentForCallOrNew(Node call, int index) { Preconditions.checkState(isCallOrNew(call)); return getNthSibling( call.getFirstChild().getNext(), index); } /** * Returns whether this is a target of a call or new. */ static boolean isCallOrNewTarget(Node target) { Node parent = target.getParent(); return parent != null && NodeUtil.isCallOrNew(parent) && parent.getFirstChild() == target; } private static boolean isToStringMethodCall(Node call) { Node getNode = call.getFirstChild(); if (isGet(getNode)) { Node propNode = getNode.getLastChild(); return propNode.isString() && "toString".equals(propNode.getString()); } return false; } /** Find the best JSDoc for the given node. */ static JSDocInfo getBestJSDocInfo(Node n) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { Node parent = n.getParent(); if (parent == null) { return null; } if (parent.isName()) { return getBestJSDocInfo(parent); } else if (parent.isAssign()) { return getBestJSDocInfo(parent); } else if (isObjectLitKey(parent)) { return parent.getJSDocInfo(); } else if (parent.isFunction()) { return parent.getJSDocInfo(); } else if (parent.isVar() && parent.hasOneChild()) { return parent.getJSDocInfo(); } else if ((parent.isHook() && parent.getFirstChild() != n) || parent.isOr() || parent.isAnd() || (parent.isComma() && parent.getFirstChild() != n)) { return getBestJSDocInfo(parent); } else if (parent

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.isCast()) { return parent.getJSDocInfo(); } } return info; } /** Find the l-value that the given r-value is being assigned to. */ static Node getBestLValue(Node n) { Node parent = n.getParent(); boolean isFunctionDeclaration = isFunctionDeclaration(n); if (isFunctionDeclaration) { return n.getFirstChild(); } else if (parent.isName()) { return parent; } else if (parent.isAssign()) { return parent.getFirstChild(); } else if (isObjectLitKey(parent)) { return parent; } else if ( (parent.isHook() && parent.getFirstChild() != n) || parent.isOr() || parent.isAnd() || (parent.isComma() && parent.getFirstChild() != n)) { return getBestLValue(parent); } else if (parent.isCast()) { return getBestLValue(parent); } return null; } /** Gets the r-value of a node returned by getBestLValue. */ static Node getRValueOfLValue(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.ASSIGN: return n.getNext(); case Token.VAR: return n.getFirstChild(); case Token.FUNCTION: return parent; } return null; } /** Get the owner of the given l-value node. */ static Node getBestLValueOwner(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue)) { return getBestLValue(lValue.getParent()); } else if (isGet(lValue)) { return lValue.getFirstChild(); } return null; } /** Get the name of the given l-value node. */ static String getBestLValueName(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue)) { Node owner = getBestLValue(lValue.getParent()); if (owner != null) { String ownerName = getBestLValueName(owner); if (ownerName != null) { return ownerName + "." + getObjectLitKeyName(lValue); } } return null; } return lValue.getQualifiedName(); } /** * @returns false iff the result of the expression is not consumed. */ static boolean isExpressionResultUsed(Node expr) { // TODO(johnlenz): consider sharing some code with trySimpleUnusedResult. Node parent = expr.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.EXPR_RESULT: return false; case Token.CAST: return isExpressionResultUsed(parent); case Token.HOOK:

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> case Token.AND: case Token.OR: return (expr == parent.getFirstChild()) ? true : isExpressionResultUsed(parent); case Token.COMMA: Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first // expression to a comma to be a no-op if it's used to indirect // an eval. This we pretend that this is "used". if (expr == parent.getFirstChild() && parent.getChildCount() == 2 && expr.getNext().isName() && "eval".equals(expr.getNext().getString())) { return true; } } return (expr == parent.getFirstChild()) ? false : isExpressionResultUsed(parent); case Token.FOR: if (!NodeUtil.isForIn(parent)) { // Only an expression whose result is in the condition part of the // expression is used. return (parent.getChildAtIndex(1) == expr); } break; } return true; } /** * @param n The expression to check. * @return Whether the expression is unconditionally executed only once in the * containing execution scope. */ static boolean isExecutedExactlyOnce(Node n) { inspect: do { Node parent = n.getParent(); switch (parent.getType()) { case Token.IF: case Token.HOOK: case Token.AND: case Token.OR: if (parent.getFirstChild() != n) { return false; } // other ancestors may be conditional continue inspect; case Token.FOR: if (NodeUtil.isForIn(parent)) { if (parent.getChildAtIndex(1) != n) { return false; } } else { if (parent.getFirstChild() != n) { return false; } } // other ancestors may be conditional continue inspect; case Token.WHILE: case Token.DO: return false; case Token.TRY: // Consider all code under a try/catch to be conditionally executed. if (!hasFinally(parent) || parent.getLastChild() != n) { return false; } continue inspect; case Token.CASE: case Token.DEFAULT_CASE: return false; case Token.SCRIPT: case Token.FUNCTION: // Done, we've reached the scope root. break inspect; } } while ((n = n.getParent()) != null); return true; } /** * @return An appropriate AST node for the boolean value. */ static Node booleanNode(boolean value) { return value ? IR.trueNode() : IR.falseNode(); } /** *

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.getFirstChild().getDouble()); } else { cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type), Context.OTHER); } break; } case Token.HOOK: { Preconditions.checkState(childCount == 3); int p = NodeUtil.precedence(type); addExpr(first, p + 1, context); cc.addOp("?", true); addExpr(first.getNext(), 1, Context.OTHER); cc.addOp(":", true); addExpr(last, 1, Context.OTHER); break; } case Token.REGEXP: if (!first.isString() || !last.isString()) { throw new Error("Expected children to be strings"); } String regexp = regexpEscape(first.getString(), outputCharsetEncoder); // I only use one .add because whitespace matters if (childCount == 2) { add(regexp + last.getString()); } else { Preconditions.checkState(childCount == 1); add(regexp); } break; case Token.FUNCTION: if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } Preconditions.checkState(childCount == 3); boolean funcNeedsParens = (context == Context.START_OF_EXPR); if (funcNeedsParens) { add("("); } add("function"); add(first); add(first.getNext()); add(last, Context.PRESERVE_BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.GETTER_DEF: case Token.SETTER_DEF: Preconditions.checkState(n.getParent().isObjectLit()); Preconditions.checkState(childCount == 1); Preconditions.checkState(first.isFunction()); // Get methods are unnamed Preconditions.checkState(first.getFirstChild().getString().isEmpty()); if (type == Token.GETTER_DEF) { // Get methods have no parameters. Preconditions.checkState(!first.getChildAtIndex(1).hasChildren()); add("get "); } else { // Set methods have one parameter. Preconditions.checkState(first.getChildAtIndex(1).hasOneChild()); add("set "); } // The name is on the GET or SET node. String name = n.getString(); Node fn = first; Node parameters = fn.getChildAtIndex(1); Node body = fn.getLastChild(); // Add the property name. if (!n.isQuotedString() && TokenStream.isJSIdentifier(name) && // do not encode literally any non-literal characters that were // Unicode escaped. NodeUtil.isLatin(name)) { add(name); } else { // Determine if the string is a

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> simple number. double d = getSimpleNumber(name); if (!Double.isNaN(d)) { cc.addNumber(d); } else { addJsString(n); } } add(parameters); add(body, Context.PRESERVE_BLOCK); break; case Token.SCRIPT: case Token.BLOCK: { if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } boolean preserveBlock = context == Context.PRESERVE_BLOCK; if (preserveBlock) { cc.beginBlock(); } boolean preferLineBreaks = type == Token.SCRIPT || (type == Token.BLOCK && !preserveBlock && n.getParent() != null && n.getParent().isScript()); for (Node c = first; c != null; c = c.getNext()) { add(c, Context.STATEMENT); // VAR doesn't include ';' since it gets used in expressions if (c.isVar()) { cc.endStatement(); } if (c.isFunction()) { cc.maybeLineBreak(); } // Prefer to break lines in between top-level statements // because top-level statements are more homogeneous. if (preferLineBreaks) { cc.notePreferredLineBreak(); } } if (preserveBlock) { cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } break; } case Token.FOR: if (childCount == 4) { add("for("); if (first.isVar()) { add(first, Context.IN_FOR_INIT_CLAUSE); } else { addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE); } add(";"); add(first.getNext()); add(";"); add(first.getNext().getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { Preconditions.checkState(childCount == 3); add("for("); add(first); add("in"); add(first.getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } break; case Token.DO: Preconditions.checkState(childCount == 2); add("do"); addNonEmptyStatement(first, Context.OTHER, false); add("while("); add(last); add(")"); cc.endStatement(); break; case Token.WHILE: Preconditions.checkState(childCount == 2); add("while("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.EMPTY: Preconditions.checkState(childCount == 0); break; case Token.GETPROP:

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>(")"); break; default: throw new Error("Unknown type " + type + "\n" + n.toStringTree()); } cc.endSourceMapping(n); } /** * We could use addList recursively here, but sometimes we produce * very deeply nested operators and run out of stack space, so we * just unroll the recursion when possible. * * We assume nodes are left-recursive. */ private void unrollBinaryOperator( Node n, int op, String opStr, Context context, Context rhsContext, int leftPrecedence, int rightPrecedence) { Node firstNonOperator = n.getFirstChild(); while (firstNonOperator.getType() == op) { firstNonOperator = firstNonOperator.getFirstChild(); } addExpr(firstNonOperator, leftPrecedence, context); Node current = firstNonOperator; do { current = current.getParent(); cc.addOp(opStr, true); addExpr(current.getFirstChild().getNext(), rightPrecedence, rhsContext); } while (current != n); } static boolean isSimpleNumber(String s) { int len = s.length(); if (len == 0) { return false; } for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c < '0' || c > '9') { return false; } } return len == 1 || s.charAt(0) != '0'; } static double getSimpleNumber(String s) { if (isSimpleNumber(s)) { try { long l = Long.parseLong(s); if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) { return l; } } catch (NumberFormatException e) { // The number was too long to parse. Fall through to NaN. } } return Double.NaN; } /** * @return Whether the name is an indirect eval. */ private boolean isIndirectEval(Node n) { return n.isName() && "eval".equals(n.getString()) && !n.getBooleanProp(Node.DIRECT_EVAL); } /** * Adds a block or expression, substituting a VOID with an empty statement. * This is used for "for (...);" and "if (...);" type statements. * * @param n The node to print. * @param context The context to determine how the node should be printed. */ private void addNonEmptyStatement( Node n, Context context, boolean allowNonBlockChild) { Node nodeToProcess = n; if (!allowNonBlockChild && !n.isBlock()) { throw new Error("Missing BLOCK child."); } // Strip unneeded blocks, that is blocks with <2 children unless // the CodePrinter specifically wants to keep them. if (n.isBlock()) { int count = getNonEmptyChildCount

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>ARATIVELY_UNBOUND_VARS_WITHOUT_TYPES = new Predicate<Var>() { @Override public boolean apply(Var var) { return var.getParentNode() != null && var.getType() == null && // no declared type var.getParentNode().isVar() && !var.isExtern(); } }; /** Stores info about a variable */ public static class Var implements StaticSlot<JSType>, StaticReference<JSType> { /** name */ final String name; /** Var node */ final Node nameNode; /** * The variable's type. */ private JSType type; /** * Whether the variable's type has been inferred or is declared. An inferred * type may change over time (as more code is discovered), whereas a * declared type is a static contract that must be matched. */ private final boolean typeInferred; /** Input source */ final CompilerInput input; /** * The index at which the var is declared. e..g if it's 0, it's the first * declared variable in that scope */ final int index; /** The enclosing scope */ final Scope scope; /** @see #isMarkedEscaped */ private boolean markedEscaped = false; /** @see #isMarkedAssignedExactlyOnce */ private boolean markedAssignedExactlyOnce = false; /** * Creates a variable. * * @param inferred whether its type is inferred (as opposed to declared) */ private Var(boolean inferred, String name, Node nameNode, JSType type, Scope scope, int index, CompilerInput input) { this.name = name; this.nameNode = nameNode; this.type = type; this.scope = scope; this.index = index; this.input = input; this.typeInferred = inferred; } /** * Gets the name of the variable. */ @Override public String getName() { return name; } /** * Gets the node for the name of the variable. */ @Override public Node getNode() { return nameNode; } CompilerInput getInput() { return input; } @Override public StaticSourceFile getSourceFile() { return nameNode.getStaticSourceFile(); } @Override public Var getSymbol() { return this; } @Override public Var getDeclaration() { return nameNode == null ? null : this; } /** * Gets the parent of the name node. */ public Node getParentNode() { return nameNode == null ? null : nameNode.getParent(); } /** * Whether this is a bleeding function (an anonymous named function * that bleeds into the inner scope). */ public boolean isBleedingFunction() { return NodeUtil.isFunctionExpression(getParentNode()); } /** * Gets the scope where this variable is declared. */ Scope getScope() { return scope; } /**

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> 0; } static Scope createGlobalScope(Node rootNode) { return new Scope(rootNode, false); } static Scope createLatticeBottom(Node rootNode) { return new Scope(rootNode, true); } /** The depth of the scope. The global scope has depth 0. */ int getDepth() { return depth; } /** Whether this is the bottom of the lattice. */ boolean isBottom() { return isBottom; } /** * Gets the container node of the scope. This is typically the FUNCTION * node or the global BLOCK/SCRIPT node. */ @Override public Node getRootNode() { return rootNode; } public Scope getParent() { return parent; } Scope getGlobalScope() { Scope result = this; while (result.getParent() != null) { result = result.getParent(); } return result; } @Override public StaticScope<JSType> getParentScope() { return parent; } /** * Gets the type of {@code this} in the current scope. */ @Override public JSType getTypeOfThis() { if (isGlobal()) { return ObjectType.cast(rootNode.getJSType()); } Preconditions.checkState(rootNode.isFunction()); JSType nodeType = rootNode.getJSType(); if (nodeType != null && nodeType.isFunctionType()) { return nodeType.toMaybeFunctionType().getTypeOfThis(); } else { return parent.getTypeOfThis(); } } /** * Declares a variable whose type is inferred. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. */ Var declare(String name, Node nameNode, JSType type, CompilerInput input) { return declare(name, nameNode, type, input, true); } /** * Declares a variable. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. * @param inferred Whether this variable's type is inferred (as opposed * to declared). */ Var declare(String name, Node nameNode, JSType type, CompilerInput input, boolean inferred) { Preconditions.checkState(name != null && name.length() > 0); // Make sure that it's declared only once Preconditions.checkState(vars.get(name) == null); Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input); vars.put(name, var); return var; } /** * Undeclares a variable, to be used when the compiler optimizes out * a variable and removes it from the scope. */ void

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>Calls( List<TweakFunctionCall> calls) { for (TweakFunctionCall call : calls) { Node callNode = call.callNode; Node objNode = createCompilerDefaultValueOverridesVarNode(callNode); callNode.getParent().replaceChild(callNode, objNode); } return !calls.isEmpty(); } /** * Removes all CALL nodes in the given TweakInfos, replacing calls to getter * functions with the tweak's default value. */ private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) { for (TweakInfo tweakInfo : tweakInfos.values()) { boolean isRegistered = tweakInfo.isRegistered(); for (TweakFunctionCall functionCall : tweakInfo.functionCalls) { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); } else { Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode)) .srcref(callNode); parent.replaceChild(callNode, voidZeroNode); } } } return !tweakInfos.isEmpty(); } /** * Creates a JS object that holds a map of tweakId -> default value override. */ private Node createCompilerDefaultValueOverridesVarNode( Node sourceInformationNode) { Node objNode = IR.objectlit().srcref(sourceInformationNode); for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { Node objKeyNode = IR.stringKey(entry.getKey()) .copyInformationFrom(sourceInformationNode); Node objValueNode = entry.getValue().cloneNode() .copyInformationFrom(sourceInformationNode); objKeyNode.addChildToBack(objValueNode); objNode.addChildToBack(objKeyNode); } return objNode; } /** Sets the default values of tweaks based on compiler options. */ private void applyCompilerDefaultValueOverrides( Map<String, TweakInfo> tweakInfos) { for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { String tweakId = entry.getKey(); TweakInfo tweakInfo = tweakInfos.get(tweakId); if (tweakInfo == null) { compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId)); } else { TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc; Node value = entry.getValue(); if (!

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> root); } // Find prototype properties that will affect our analysis. Preconditions.checkState(namespace.hasExternsRoot()); findPrototypeProps("Object", objectPrototypeProps); findPrototypeProps("Function", functionPrototypeProps); objectPrototypeProps.addAll( convention.getIndirectlyDeclaredProperties()); for (Name name : namespace.getNameForest()) { // Skip extern names. Externs are often not runnable as real code, // and will do things like: // var x; // x.method; // which this check forbids. if (name.inExterns) { continue; } checkDescendantNames(name, name.globalSets + name.localSets > 0); } } private void findPrototypeProps(String type, Set<String> props) { Name slot = namespace.getSlot(type); if (slot != null) { for (Ref ref : slot.getRefs()) { if (ref.type == Ref.Type.PROTOTYPE_GET) { Node fullName = ref.getNode().getParent().getParent(); if (fullName.isGetProp()) { props.add(fullName.getLastChild().getString()); } } } } } /** * Checks to make sure all the descendants of a name are defined if they * are referenced. * * @param name A global name. * @param nameIsDefined If true, {@code name} is defined. Otherwise, it's * undefined, and any references to descendant names should emit warnings. */ private void checkDescendantNames(Name name, boolean nameIsDefined) { if (name.props != null) { for (Name prop : name.props) { // if the ancestor of a property is not defined, then we should emit // warnings for all references to the property. boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.getDeclaration(); Name parent = name.parent; JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.getRefs()) { // Don't worry about global exprs. boolean isGlobalExpr = ref.getNode().getParent().isExprResult(); if (!isDefined && !isTypedef(ref)) { if (!isGlobalExpr) { reportRefToUndefinedName(name, ref); } } else

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> if (declaration != null && ref.getModule() != declaration.getModule() && !moduleGraph.dependsOn( ref.getModule(), declaration.getModule())) { reportBadModuleReference(name, ref); } else { // Check for late references. if (ref.scope.isGlobal()) { // Prototype references are special, because in our reference graph, // A.prototype counts as a reference to A. boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET); Name owner = isPrototypeGet ? name : parent; boolean singleGlobalParentDecl = owner != null && owner.getDeclaration() != null && owner.localSets == 0; if (singleGlobalParentDecl && owner.getDeclaration().preOrderIndex > ref.preOrderIndex) { String refName = isPrototypeGet ? name.getFullName() + ".prototype" : name.getFullName(); compiler.report( JSError.make(ref.source.getName(), ref.node, NAME_DEFINED_LATE_WARNING, refName, owner.getFullName(), owner.getDeclaration().source.getName(), String.valueOf(owner.getDeclaration().node.getLineno()))); } } } } } private boolean isTypedef(Ref ref) { // If this is an annotated EXPR-GET, don't do anything. Node parent = ref.node.getParent(); if (parent.isExprResult()) { JSDocInfo info = ref.node.getJSDocInfo(); if (info != null && info.hasTypedefType()) { return true; } } return false; } private void reportBadModuleReference(Name name, Ref ref) { compiler.report( JSError.make(ref.source.getName(), ref.node, STRICT_MODULE_DEP_QNAME, ref.getModule().getName(), name.getDeclaration().getModule().getName(), name.getFullName())); } private void reportRefToUndefinedName(Name name, Ref ref) { // grab the highest undefined ancestor to output in the warning message. while (name.parent != null && name.parent.globalSets + name.parent.localSets == 0) { name = name.parent; } compiler.report( JSError.make(ref.getSourceName(), ref.node, level, UNDEFINED_NAME_WARNING, name.getFullName())); } /** * Checks whether the given name is a property, and whether that property * must be initialized with its full qualified name. */ private boolean propertyMustBeInitializedByFullName(Name name) { // If an object or function literal in the global namespace is never // aliased, then its properties can only come from one of 2 places: // 1) From its prototype chain, or // 2) From an assignment to its fully qualified name. // If we assume #1 is not the case, then #2 implies

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> that its // properties must all be modeled in the GlobalNamespace as well. // // We assume that for global object literals and types (constructors and // interfaces), we can find all the properties inherited from the prototype // chain of functions and objects. if (name.parent == null) { return false; } boolean parentIsAliased = false; if (name.parent.aliasingGets > 0) { for (Ref ref : name.parent.getRefs()) { if (ref.type == Ref.Type.ALIASING_GET) { Node aliaser = ref.getNode().getParent(); // We don't need to worry about known aliased, because // they're already covered by the getIndirectlyDeclaredProperties // call at the top. boolean isKnownAlias = aliaser.isCall() && (convention.getClassesDefinedByCall(aliaser) != null || convention.getSingletonGetterClassName(aliaser) != null); if (!isKnownAlias) { parentIsAliased = true; } } } } if (parentIsAliased) { return false; } if (objectPrototypeProps.contains(name.getBaseName())) { return false; } if (name.parent.type == Name.Type.OBJECTLIT) { return true; } if (name.parent.type == Name.Type.FUNCTION && name.parent.isDeclaredType() && !functionPrototypeProps.contains(name.getBaseName())) { return true; } return false; } }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.intValue = intValue; } @Override public int getIntValue() { return intValue; } @Override public Object getObjectValue() { throw new UnsupportedOperationException(); } @Override public String toString() { return String.valueOf(intValue); } @Override public PropListItem chain(PropListItem next) { return new IntPropListItem(getType(), intValue, next); } } public Node(int nodeType) { type = nodeType; parent = null; sourcePosition = -1; } public Node(int nodeType, Node child) { Preconditions.checkArgument(child.parent == null, "new child has existing parent"); Preconditions.checkArgument(child.next == null, "new child has existing sibling"); type = nodeType; parent = null; first = last = child; child.next = null; child.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node right) { Preconditions.checkArgument(left.parent == null, "first new child has existing parent"); Preconditions.checkArgument(left.next == null, "first new child has existing sibling"); Preconditions.checkArgument(right.parent == null, "second new child has existing parent"); Preconditions.checkArgument(right.next == null, "second new child has existing sibling"); type = nodeType; parent = null; first = left; last = right; left.next = right; left.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent = this; mid.next = right; mid.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node mid2, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(mid2.parent == null); Preconditions.checkArgument(mid2.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null;

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> first = left; last = right; left.next = mid; left.parent = this; mid.next = mid2; mid.parent = this; mid2.next = right; mid2.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, int lineno, int charno) { type = nodeType; parent = null; sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node child, int lineno, int charno) { this(nodeType, child); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node right, int lineno, int charno) { this(nodeType, left, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node right, int lineno, int charno) { this(nodeType, left, mid, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node mid2, Node right, int lineno, int charno) { this(nodeType, left, mid, mid2, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children, int lineno, int charno) { this(nodeType, children); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children) { this.type = nodeType; parent = null; if (children.length != 0) { this.first = children[0]; this.last = children[children.length - 1]; for (int i = 1; i < children.length; i++) { if (null != children[i - 1].next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } children[i - 1].next = children[i]; Preconditions.checkArgument(children[i - 1].parent == null); children[i - 1].parent = this; } Preconditions.checkArgument(children[children.length - 1].parent == null); children[children.length - 1].parent = this; if (null != this.last.next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } } } public static Node newNumber(double number) { return new NumberNode(number); } public static Node newNumber(double number, int lineno, int charno) { return new NumberNode(number, lineno, charno); } public static Node newString(String str) { return new String

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>Node(Token.STRING, str); } public static Node newString(int type, String str) { return new StringNode(type, str); } public static Node newString(String str, int lineno, int charno) { return new StringNode(Token.STRING, str, lineno, charno); } public static Node newString(int type, String str, int lineno, int charno) { return new StringNode(type, str, lineno, charno); } public int getType() { return type; } public void setType(int type) { this.type = type; } public boolean hasChildren() { return first != null; } public Node getFirstChild() { return first; } public Node getLastChild() { return last; } public Node getNext() { return next; } public Node getChildBefore(Node child) { if (child == first) { return null; } Node n = first; while (n.next != child) { n = n.next; if (n == null) { throw new RuntimeException("node is not a child"); } } return n; } public Node getChildAtIndex(int i) { Node n = first; while (i > 0) { n = n.next; i--; } return n; } public int getIndexOfChild(Node child) { Node n = first; int i = 0; while (n != null) { if (child == n) { return i; } n = n.next; i++; } return -1; } public Node getLastSibling() { Node n = this; while (n.next != null) { n = n.next; } return n; } public void addChildToFront(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = first; first = child; if (last == null) { last = child; } } public void addChildToBack(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = null; if (last == null) { first = last = child; return; } last.next = child; last = child; } public void addChildrenToFront(Node children) { for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } Node lastSib = children.getLastSibling(); lastSib.next = first; first = children; if (last == null) { last = lastSib

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>; } } public void addChildrenToBack(Node children) { addChildrenAfter(children, getLastChild()); } /** * Add 'child' before 'node'. */ public void addChildBefore(Node newChild, Node node) { Preconditions.checkArgument(node != null && node.parent == this, "The existing child node of the parent should not be null."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); if (first == node) { newChild.parent = this; newChild.next = first; first = newChild; return; } Node prev = getChildBefore(node); addChildAfter(newChild, prev); } /** * Add 'child' after 'node'. */ public void addChildAfter(Node newChild, Node node) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); addChildrenAfter(newChild, node); } /** * Add all children after 'node'. */ public void addChildrenAfter(Node children, Node node) { Preconditions.checkArgument(node == null || node.parent == this); for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } Node lastSibling = children.getLastSibling(); if (node != null) { Node oldNext = node.next; node.next = children; lastSibling.next = oldNext; if (node == last) { last = lastSibling; } } else { // Append to the beginning. if (first != null) { lastSibling.next = first; } else { last = lastSibling; } first = children; } } /** * Detach a child from its parent and siblings. */ public void removeChild(Node child) { Node prev = getChildBefore(child); if (prev == null) { first = first.next; } else { prev.next = child.next; } if (child == last) { last = prev; } child.next = null; child.parent = null; } /** * Detaches child from Node and replaces it with newChild. */ public void replaceChild(Node child, Node newChild) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(child); newChild.next = child.next; newChild.parent = this; if (child == first) {

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> first = newChild; } else { Node prev = getChildBefore(child); prev.next = newChild; } if (child == last) { last = newChild; } child.next = null; child.parent = null; } public void replaceChildAfter(Node prevChild, Node newChild) { Preconditions.checkArgument(prevChild.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(prevChild); Node child = prevChild.next; newChild.next = child.next; newChild.parent = this; prevChild.next = newChild; if (child == last) { last = newChild; } child.next = null; child.parent = null; } @VisibleForTesting PropListItem lookupProperty(int propType) { PropListItem x = propListHead; while (x != null && propType != x.getType()) { x = x.getNext(); } return x; } /** * Clone the properties from the provided node without copying * the property object. The receiving node may not have any * existing properties. * @param other The node to clone properties from. * @return this node. */ public Node clonePropsFrom(Node other) { Preconditions.checkState(this.propListHead == null, "Node has existing properties."); this.propListHead = other.propListHead; return this; } public void removeProp(int propType) { PropListItem result = removeProp(propListHead, propType); if (result != propListHead) { propListHead = result; } } /** * @param item The item to inspect * @param propType The property to look for * @return The replacement list if the property was removed, or * 'item' otherwise. */ private PropListItem removeProp(PropListItem item, int propType) { if (item == null) { return null; } else if (item.getType() == propType) { return item.getNext(); } else { PropListItem result = removeProp(item.getNext(), propType); if (result != item.getNext()) { return item.chain(result); } else { return item; } } } public Object getProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return null; } return item.getObjectValue(); } public boolean getBooleanProp(int propType) { return getIntProp(propType) != 0; } /** * Returns the integer value for the property

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> /** Returns the source file associated with this input. May be null */ public StaticSourceFile getStaticSourceFile() { return ((StaticSourceFile) this.getProp(STATIC_SOURCE_FILE)); } /** * @param inputId */ public void setInputId(InputId inputId) { this.putProp(INPUT_ID, inputId); } /** * @return The Id of the CompilerInput associated with this Node. */ public InputId getInputId() { return ((InputId) this.getProp(INPUT_ID)); } public boolean isFromExterns() { StaticSourceFile file = getStaticSourceFile(); return file == null ? false : file.isExtern(); } public int getLength() { return getIntProp(LENGTH); } public void setLength(int length) { putIntProp(LENGTH, length); } public int getLineno() { return extractLineno(sourcePosition); } public int getCharno() { return extractCharno(sourcePosition); } public int getSourceOffset() { StaticSourceFile file = getStaticSourceFile(); if (file == null) { return -1; } int lineno = getLineno(); if (lineno == -1) { return -1; } return file.getLineOffset(lineno) + getCharno(); } public int getSourcePosition() { return sourcePosition; } public void setLineno(int lineno) { int charno = getCharno(); if (charno == -1) { charno = 0; } sourcePosition = mergeLineCharNo(lineno, charno); } public void setCharno(int charno) { sourcePosition = mergeLineCharNo(getLineno(), charno); } public void setSourceEncodedPosition(int sourcePosition) { this.sourcePosition = sourcePosition; } public void setSourceEncodedPositionForTree(int sourcePosition) { this.sourcePosition = sourcePosition; for (Node child = getFirstChild(); child != null; child = child.getNext()) { child.setSourceEncodedPositionForTree(sourcePosition); } } /** * Merges the line number and character number in one integer. The Character * number takes the first 12 bits and the line number takes the rest. If * the character number is greater than <code>2<sup>12</sup>-1</code> it is * adjusted to <code>2<sup>12</sup>-1</code>. */ protected static int mergeLineCharNo(int lineno, int charno) { if (lineno < 0 || charno < 0) { return -1; } else if ((charno & ~COLUMN_MASK) != 0) { return lineno << COLUMN_BITS | COLUMN_MASK; } else { return lineno << COLUMN_BITS | (charno & COLUMN_MASK); }

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> { current = current.getNext(); } } @Override public void remove() { throw new UnsupportedOperationException(); } } // ========================================================================== // Accessors PropListItem getPropListHeadForTesting() { return propListHead; } public Node getParent() { return parent; } /** * Gets the ancestor node relative to this. * * @param level 0 = this, 1 = the parent, etc. */ public Node getAncestor(int level) { Preconditions.checkArgument(level >= 0); Node node = this; while (node != null && level-- > 0) { node = node.getParent(); } return node; } /** * Iterates all of the node's ancestors excluding itself. */ public AncestorIterable getAncestors() { return new AncestorIterable(this.getParent()); } /** * Iterator to go up the ancestor tree. */ public static class AncestorIterable implements Iterable<Node> { private Node cur; /** * @param cur The node to start. */ AncestorIterable(Node cur) { this.cur = cur; } @Override public Iterator<Node> iterator() { return new Iterator<Node>() { @Override public boolean hasNext() { return cur != null; } @Override public Node next() { if (!hasNext()) { throw new NoSuchElementException(); } Node n = cur; cur = cur.getParent(); return n; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Check for one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * * @return Whether the node has exactly one child. */ public boolean hasOneChild() { return first != null && first == last; } /** * Check for more than one child more efficiently than by iterating over all * the children as is done with Node.getChildCount(). * * @return Whether the node more than one child. */ public boolean hasMoreThanOneChild() { return first != null && first != last; } public int getChildCount() { int c = 0; for (Node n = first; n != null; n = n.next) { c++; } return c; } // Intended for testing and verification only. public boolean hasChild(Node child) { for (Node n = first; n != null; n = n.getNext()) { if (child == n) { return true; } } return false; } /** * Checks if the subtree under this node is the same as another subtree. * Returns null if it's equal, or a message describing the differences. */ public String checkTreeEquals(Node node2) { NodeMismatch diff = checkTreeEqualsImpl(node2);

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> a simple or a qualified name, such as * <code>x</code> or <code>a.b.c</code> or <code>this.a</code>. */ public boolean isQualifiedName() { switch (getType()) { case Token.NAME: return getString().isEmpty() ? false : true; case Token.THIS: return true; case Token.GETPROP: return getFirstChild().isQualifiedName(); default: return false; } } /** * Returns whether a node corresponds to a simple or a qualified name without * a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code> * . */ public boolean isUnscopedQualifiedName() { switch (getType()) { case Token.NAME: return getString().isEmpty() ? false : true; case Token.GETPROP: return getFirstChild().isUnscopedQualifiedName(); default: return false; } } // ========================================================================== // Mutators /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public Node detachFromParent() { Preconditions.checkState(parent != null); parent.removeChild(this); return this; } /** * Removes the first child of Node. Equivalent to: * node.removeChild(node.getFirstChild()); * * @return The removed Node. */ public Node removeFirstChild() { Node child = first; if (child != null) { removeChild(child); } return child; } /** * @return A Node that is the head of the list of children. */ public Node removeChildren() { Node children = first; for (Node child = first; child != null; child = child.getNext()) { child.parent = null; } first = null; last = null; return children; } /** * Removes all children from this node and isolates the children from each * other. */ public void detachChildren() { for (Node child = first; child != null;) { Node nextChild = child.getNext(); child.parent = null; child.next = null; child = nextChild; } first = null; last = null; } public Node removeChildAfter(Node prev) { Preconditions.checkArgument(prev.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(prev.next != null, "no next sibling."); Node child = prev.next; prev.next = child.next; if (child == last) { last = prev; } child.next = null; child.parent = null; return child; } /** * @return A detached clone of the Node, specifically excluding its children. */ public Node cloneNode() { Node result; try { result

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> = (Node) super.clone(); // PropListItem lists are immutable and can be shared so there is no // need to clone them here. result.next = null; result.first = null; result.last = null; result.parent = null; } catch (CloneNotSupportedException e) { throw new RuntimeException(e.getMessage()); } return result; } /** * @return A detached clone of the Node and all its children. */ public Node cloneTree() { Node result = cloneNode(); for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) { Node n2clone = n2.cloneTree(); n2clone.parent = result; if (result.last != null) { result.last.next = n2clone; } if (result.first == null) { result.first = n2clone; } result.last = n2clone; } return result; } /** * Copies source file and name information from the other * node given to the current node. Used for maintaining * debug information across node append and remove operations. * @return this */ // TODO(nicksantos): The semantics of this method are ill-defined. Delete it. public Node copyInformationFrom(Node other) { if (getProp(ORIGINALNAME_PROP) == null) { putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP)); } if (getProp(STATIC_SOURCE_FILE) == null) { putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE)); sourcePosition = other.sourcePosition; } return this; } /** * Copies source file and name information from the other node to the * entire tree rooted at this node. * @return this */ // TODO(nicksantos): The semantics of this method are ill-defined. Delete it. public Node copyInformationFromForTree(Node other) { copyInformationFrom(other); for (Node child = getFirstChild(); child != null; child = child.getNext()) { child.copyInformationFromForTree(other); } return this; } /** * Overwrite all the source information in this node with * that of {@code other}. */ public Node useSourceInfoFrom(Node other) { putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP)); putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE)); sourcePosition = other.sourcePosition; return this; } public Node srcref(Node other) { return useSourceInfoFrom(other); } /** * Overwrite all the source information in this node and its subtree with * that of {@code other}. */ public Node useSourceInfoFromForTree(Node other)

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> { useSourceInfoFrom(other); for (Node child = getFirstChild(); child != null; child = child.getNext()) { child.useSourceInfoFromForTree(other); } return this; } public Node srcrefTree(Node other) { return useSourceInfoFromForTree(other); } /** * Overwrite all the source information in this node with * that of {@code other} iff the source info is missing. */ public Node useSourceInfoIfMissingFrom(Node other) { if (getProp(ORIGINALNAME_PROP) == null) { putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP)); } if (getProp(STATIC_SOURCE_FILE) == null) { putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE)); sourcePosition = other.sourcePosition; } return this; } /** * Overwrite all the source information in this node and its subtree with * that of {@code other} iff the source info is missing. */ public Node useSourceInfoIfMissingFromForTree(Node other) { useSourceInfoIfMissingFrom(other); for (Node child = getFirstChild(); child != null; child = child.getNext()) { child.useSourceInfoIfMissingFromForTree(other); } return this; } //========================================================================== // Custom annotations public JSType getJSType() { return jsType; } public void setJSType(JSType jsType) { this.jsType = jsType; } public FileLevelJsDocBuilder getJsDocBuilderForNode() { return new FileLevelJsDocBuilder(); } /** * An inner class that provides back-door access to the license * property of the JSDocInfo property for this node. This is only * meant to be used for top-level script nodes where the * {@link com.google.javascript.jscomp.parsing.JsDocInfoParser} needs to * be able to append directly to the top-level node, not just the * current node. */ public class FileLevelJsDocBuilder { public void append(String fileLevelComment) { JSDocInfo jsDocInfo = getJSDocInfo(); if (jsDocInfo == null) { // TODO(user): Is there a way to determine whether to // parse the JsDoc documentation from here? jsDocInfo = new JSDocInfo(false); } String license = jsDocInfo.getLicense(); if (license == null) { license = ""; } jsDocInfo.setLicense(license + fileLevelComment); setJSDocInfo(jsDocInfo); } } /** * Get the {@link JSDocInfo} attached to this node. * @return the information or {@code null} if no JSDoc is attached to this * node */ public

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> connectToPossibleExceptionHandler(iter, iter); } else { // We have for (item in collection) { body } Node item = forNode.getFirstChild(); Node collection = item.getNext(); Node body = collection.getNext(); // The collection behaves like init. createEdge(collection, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); connectToPossibleExceptionHandler(forNode, collection); } } private void handleSwitch(Node node) { // Transfer to the first non-DEFAULT CASE. if there are none, transfer // to the DEFAULT or the EMPTY node. Node next = getNextSiblingOfType( node.getFirstChild().getNext(), Token.CASE, Token.EMPTY); if (next != null) { // Has at least one CASE or EMPTY createEdge(node, Branch.UNCOND, next); } else { // Has no CASE but possibly a DEFAULT if (node.getFirstChild().getNext() != null) { createEdge(node, Branch.UNCOND, node.getFirstChild().getNext()); } else { // No CASE, no DEFAULT createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleCase(Node node) { // Case is a bit tricky....First it goes into the body if condition is true. createEdge(node, Branch.ON_TRUE, node.getFirstChild().getNext()); // Look for the next CASE, skipping over DEFAULT. Node next = getNextSiblingOfType(node.getNext(), Token.CASE); if (next != null) { // Found a CASE Preconditions.checkState(next.isCase()); createEdge(node, Branch.ON_FALSE, next); } else { // No more CASE found, go back and search for a DEFAULT. Node parent = node.getParent(); Node deflt = getNextSiblingOfType( parent.getFirstChild().getNext(), Token.DEFAULT_CASE); if (deflt != null) { // Has a DEFAULT createEdge(node, Branch.ON_FALSE, deflt); } else { // No DEFAULT found, go to the follow of the SWITCH. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleDefault(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleWith(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.UNCOND, node.getLastChild()); connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleStmtList(Node node) { Node parent = node.getParent(); // Special case, don't add a block of empty CATCH block to the graph. if (node.isBlock() && parent != null && parent.isTry() && NodeUtil.getCatchBlock(parent) == node && !NodeUtil.hasCatchHandler(node)) { return; } // A block transfer control to its first child if it is not empty. Node child = node.getFirstChild(); // Function declarations are skipped since control doesn't go into that // function (unless it is called) while (child != null && child.isFunction()) { child = child.getNext(); } if (child != null) { createEdge(node, Branch.UNCOND, computeFallThrough(child)); } else { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } // Synthetic blocks if (parent != null) { switch (parent.getType()) { case Token.DEFAULT_CASE: case Token.CASE: case Token.TRY: break; default: if (node.isBlock() && node.isSyntheticBlock()) { createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this)); } break; } } } private void handleFunction(Node node) { // A block transfer control to its first child if it is not empty. Preconditions.checkState(node.getChildCount() >= 3); createEdge(node, Branch.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext())); Preconditions.checkState(exceptionHandler.peek() == node); exceptionHandler.pop(); } private void handleExpr(Node node) { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } private void handleThrow(Node node) { connectToPossibleExceptionHandler(node, node); } private void handleTry(Node node) { createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleCatch(Node node) { createEdge(node, Branch.UNCOND, node.getLastChild()); } private void handleBreak(Node node) { String label = null; // See if it is a break with label. if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node previous = null; Node lastJump; Node parent = node.getParent(); /* * Continuously look up the ancestor tree for the BREAK target or the target * with the corresponding label and connect to it. If along the path we * discover a FINALLY, we will connect the BREAK to that FINALLY. From then * on, we will just record the control

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> flow changes in the finallyMap. This * is due to the fact that we need to connect any node that leaves its own * FINALLY block to the outer FINALLY or the BREAK's target but those nodes * are not known yet due to the way we traverse the nodes. */ for (cur = node, lastJump = node; !isBreakTarget(cur, label); cur = parent, parent = parent.getParent()) { if (cur.isTry() && NodeUtil.hasFinally(cur) && cur.getLastChild() != previous) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFallThrough( cur.getLastChild())); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } if (parent == null) { if (compiler.isIdeMode()) { // In IDE mode, we expect that the data flow graph may // not be well-formed. return; } else { throw new IllegalStateException("Cannot find break target."); } } previous = cur; } if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this)); } else { finallyMap.put(lastJump, computeFollowNode(cur, this)); } } private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node previous = null; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.isTry() && NodeUtil.hasFinally(cur) && cur.getLastChild() != previous) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); previous = cur; } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } } private void handleReturn(Node node) { Node lastJump = null; for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) { Node curHandler = iter.next(); if (curHandler.isFunction()) { break; } if (NodeUtil

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.hasFinally(curHandler)) { if (lastJump == null) { createEdge(node, Branch.UNCOND, curHandler.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(curHandler.getLastChild())); } lastJump = curHandler; } } if (node.hasChildren()) { connectToPossibleExceptionHandler(node, node.getFirstChild()); } if (lastJump == null) { createEdge(node, Branch.UNCOND, null); } else { finallyMap.put(lastJump, null); } } private void handleStmt(Node node) { // Simply transfer to the next line. createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) { return computeFollowNode(node, node, cfa); } static Node computeFollowNode(Node node) { return computeFollowNode(node, node, null); } /** * Computes the follow() node of a given node and its parent. There is a side * effect when calling this function. If this function computed an edge that * exists a FINALLY, it'll attempt to connect the fromNode to the outer * FINALLY according to the finallyMap. * * @param fromNode The original source node since {@code node} is changed * during recursion. * @param node The node that follow() should compute. */ private static Node computeFollowNode( Node fromNode, Node node, ControlFlowAnalysis cfa) { /* * This is the case where: * * 1. Parent is null implies that we are transferring control to the end of * the script. * * 2. Parent is a function implies that we are transferring control back to * the caller of the function. * * 3. If the node is a return statement, we should also transfer control * back to the caller of the function. * * 4. If the node is root then we have reached the end of what we have been * asked to traverse. * * In all cases we should transfer control to a "symbolic return" node. * This will make life easier for DFAs. */ Node parent = node.getParent(); if (parent == null || parent.isFunction() || (cfa != null && node == cfa.root)) { return null; } // If we are just before a IF/WHILE/DO/FOR: switch (parent.getType()) { // The follow() of any of the path from IF would be what follows IF. case Token.IF: return computeFollowNode(fromNode, parent, cfa); case Token.CASE: case Token.DEFAULT_CASE: // After the body of a CASE, the control goes to the body of the next // case,

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } } /** * Connects the two nodes in the control flow graph. * * @param fromNode Source. * @param toNode Destination. */ private void createEdge(Node fromNode, ControlFlowGraph.Branch branch, Node toNode) { cfg.createNode(fromNode); cfg.createNode(toNode); cfg.connectIfNotFound(fromNode, branch, toNode); } /** * Connects cfgNode to the proper CATCH block if target subtree might throw * an exception. If there are FINALLY blocks reached before a CATCH, it will * make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (handler.isFunction()) { return; } Preconditions.checkState(handler.isTry()); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } } /** * Get the next sibling (including itself) of one of the given types. */ private static Node getNextSiblingOfType(Node first, int ... types) { for (Node c = first; c != null; c = c.getNext()) { for (int type : types) { if (c.getType() == type) { return c; } } } return null; } /** * Checks if target is actually the break target of labeled continue. The * label can be null if it is an unlabeled break. */ public static boolean isBreakTarget(Node target, String label) { return isBreakStructure(target, label != null) && matchLabel(target.getParent(), label); } /** * Checks if target is actually the continue target of labeled continue. The * label can be null if it is an unlabeled continue. */ private static boolean isContinueTarget( Node target, Node parent, String label) { return isContinueStructure(target) && matchLabel(parent, label); } /** * Check if label is actually referencing the target control structure. If * label is null, it always returns true. */ private static boolean

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> matchLabel(Node target, String label) { if (label == null) { return true; } while (target.isLabel()) { if (target.getFirstChild().getString().equals(label)) { return true; } target = target.getParent(); } return false; } /** * Determines if the subtree might throw an exception. */ public static boolean mayThrowException(Node n) { switch (n.getType()) { case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.THROW: case Token.NEW: case Token.ASSIGN: case Token.INC: case Token.DEC: case Token.INSTANCEOF: return true; case Token.FUNCTION: return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) { return true; } } return false; } /** * Determines whether the given node can be terminated with a BREAK node. */ static boolean isBreakStructure(Node n, boolean labeled) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.SWITCH: return true; case Token.BLOCK: case Token.IF: case Token.TRY: return labeled; default: return false; } } /** * Determines whether the given node can be advanced with a CONTINUE node. */ static boolean isContinueStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * Get the TRY block with a CATCH that would be run if n throws an exception. * @return The CATCH node or null if it there isn't a CATCH before the * the function terminates. */ static Node getExceptionHandler(Node n) { for (Node cur = n; !cur.isScript() && !cur.isFunction(); cur = cur.getParent()) { Node catchNode = getCatchHandlerForBlock(cur); if (catchNode != null) { return catchNode; } } return null; } /** * Locate the catch BLOCK given the first block in a TRY. * @return The CATCH node or null there is no catch handler. */ static Node getCatchHandlerForBlock(Node block) { if (block.isBlock() && block.getParent().isTry() && block.getParent().getFirstChild() == block) { for (Node s = block.getNext(); s != null; s = s.getNext()) { if (NodeUtil.hasCatchHandler(s)) { return s.getFirstChild(); } } } return null

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> templatized; extend the logic in this function when * more types can be templatized. * @param baseType the type to be templatized. * @param templatizedTypes a list of the template JSTypes. Will be matched by * list order to the template keys on the base type. */ public TemplatizedType createTemplatizedType( ObjectType baseType, ImmutableList<JSType> templatizedTypes) { // Only ObjectTypes can currently be templatized; extend this logic when // more types can be templatized. return new TemplatizedType(this, baseType, templatizedTypes); } /** * Creates a templatized instance of the specified type. Only ObjectTypes * can currently be templatized; extend the logic in this function when * more types can be templatized. * @param baseType the type to be templatized. * @param templatizedTypes a list of the template JSTypes. Will be matched by * list order to the template keys on the base type. */ public TemplatizedType createTemplatizedType( ObjectType baseType, JSType... templatizedTypes) { return createTemplatizedType( baseType, ImmutableList.copyOf(templatizedTypes)); } /** * Creates a named type. */ @VisibleForTesting public JSType createNamedType(String reference, String sourceName, int lineno, int charno) { return new NamedType(this, reference, sourceName, lineno, charno); } /** * Identifies the name of a typedef or enum before we actually declare it. */ public void identifyNonNullableName(String name) { Preconditions.checkNotNull(name); nonNullableTypeNames.add(name); } /** * Creates a JSType from the nodes representing a type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ public JSType createFromTypeNodes(Node n, String sourceName, StaticScope<JSType> scope) { if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) { // If the type expression doesn't contain any names, just // resolve it anyway. boolean hasNames = hasTypeName(n); if (hasNames) { return new UnresolvedTypeExpression(this, n, sourceName); } } return createFromTypeNodesInternal(n, sourceName, scope); } private boolean hasTypeName(Node n) { if (n.getType() == Token.STRING) { return true; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (hasTypeName(child)) { return true; } } return false; } /** @see #createFromTypeNodes(Node, String, StaticScope) */ private JSType createFrom

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>TypeNodesInternal(Node n, String sourceName, StaticScope<JSType> scope) { switch (n.getType()) { case Token.LC: // Record type. return createRecordTypeFromNodes( n.getFirstChild(), sourceName, scope); case Token.BANG: // Not nullable return createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope) .restrictByNotNullOrUndefined(); case Token.QMARK: // Nullable or unknown Node firstChild = n.getFirstChild(); if (firstChild == null) { return getNativeType(UNKNOWN_TYPE); } return createDefaultObjectUnion( createFromTypeNodesInternal( firstChild, sourceName, scope)); case Token.EQUALS: // Optional return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope)); case Token.ELLIPSIS: // Var args return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope)); case Token.STAR: // The AllType return getNativeType(ALL_TYPE); case Token.LB: // Array type // TODO(nicksantos): Enforce membership restrictions on the Array. return getNativeType(ARRAY_TYPE); case Token.PIPE: // Union type UnionTypeBuilder builder = new UnionTypeBuilder(this); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { builder.addAlternate( createFromTypeNodesInternal(child, sourceName, scope)); } return builder.build(); case Token.EMPTY: // When the return value of a function is not specified return getNativeType(UNKNOWN_TYPE); case Token.VOID: // Only allowed in the return value of a function. return getNativeType(VOID_TYPE); case Token.STRING: JSType namedType = getType(scope, n.getString(), sourceName, n.getLineno(), n.getCharno()); if (resolveMode != ResolveMode.LAZY_NAMES) { namedType = namedType.resolveInternal(reporter, scope); } if ((namedType instanceof ObjectType) && !(nonNullableTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); int nAllowedTypes = namedType.getTemplateTypeMap().numUnfilledTemplateKeys(); if (typeList != null && nAllowedTypes > 0) { // Templatized types. ImmutableList.Builder<JSType> templateTypes = ImmutableList.builder(); // Special case for Object, where Object.<X> implies Object.<?,X>. if (n.getString().equals("Object") && typeList.getFirstChild() == typeList.getLastChild()) { templateTypes.add(getNativeType(UNKNOWN_TYPE)); } int templateNodeIndex = 0; for (Node templateNode : typeList.getFirstChild().siblings()) { // Don't parse more templatized type nodes

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> /** * Gets a comparator for the nodes. The default implementation returns * {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}. * @param isForward Whether the comparator sorts the nodes in the direction of * the flow. * @return a comparator or null (in particular, if not overridden) */ public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator( boolean isForward) { return null; } /** * The edge object for the control flow graph. */ public static enum Branch { /** Edge is taken if the condition is true. */ ON_TRUE, /** Edge is taken if the condition is false. */ ON_FALSE, /** Unconditional branch. */ UNCOND, /** * Exception-handling code paths. * Conflates two kind of control flow passing: * - An exception is thrown, and falls into a catch or finally block * - During exception handling, a finally block finishes and control * passes to the next finally block. * In theory, we need 2 different edge types. In practice, we * can just treat them as "the edges we can't really optimize". */ ON_EX, /** Possible folded-away template */ SYN_BLOCK; public boolean isConditional() { return this == ON_TRUE || this == ON_FALSE; } } /** * Abstract callback to visit a control flow graph node without going into * subtrees of the node that are also represented by other * control flow graph nodes. * * <p>For example, traversing an IF node as root will visit the two subtrees * pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and * {@link ControlFlowGraph.Branch#ON_FALSE} edges. */ public abstract static class AbstractCfgNodeTraversalCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (parent == null) { return true; } return !isEnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // bleeds into the local scope and parameters are assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body, represented by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>, ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE); declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE); declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE); declareNativeValueType(s, "undefined", VOID_TYPE); // There is no longer a need to special case ActiveXObject // but this remains here until we can get the extern forks // cleaned up. declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE); return s; } private void declareNativeFunctionType(Scope scope, JSTypeNative tId) { FunctionType t = typeRegistry.getNativeFunctionType(tId); declareNativeType(scope, t.getInstanceType().getReferenceName(), t); declareNativeType( scope, t.getPrototype().getReferenceName(), t.getPrototype()); } private void declareNativeValueType(Scope scope, String name, JSTypeNative tId) { declareNativeType(scope, name, typeRegistry.getNativeType(tId)); } private void declareNativeType(Scope scope, String name, JSType t) { scope.declare(name, null, t, null, false); } private static class DiscoverEnumsAndTypedefs extends AbstractShallowStatementCallback { private final JSTypeRegistry registry; DiscoverEnumsAndTypedefs(JSTypeRegistry registry) { this.registry = registry; } @Override public void visit(NodeTraversal t, Node node, Node parent) { switch (node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, NodeUtil.getBestJSDocInfo(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.isAssign()) { identifyNameNode( firstChild.getFirstChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> { // Handle hoisted functions on pre-order traversal, so that they // get hit before other things in the scope. if (NodeUtil.isStatementParent(n)) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (NodeUtil.isHoistedFunctionDeclaration(child)) { defineFunctionLiteral(child); } } } } return descend; } @Override public void visit(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); attachLiteralTypes(n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n); checkForCallingConventionDefiningCalls(n, delegateCallingConventions); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // Hoisted functions are handled during pre-traversal. if (!NodeUtil.isHoistedFunctionDeclaration(n)) { defineFunctionLiteral(n); } break; case Token.ASSIGN: // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.isGetProp() && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n); break; case Token.VAR: defineVar(n); break; case Token.GETPROP: // Handle stubbed properties. if (parent.isExprResult() && n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } // Analyze any @lends object literals in this statement. if (n.getParent() != null && NodeUtil.isStatement(n) && lentObjectLiterals != null) { for (Node objLit : lentObjectLiterals) { defineObjectLiteral(objLit); } lentObjectLiterals.clear(); } } private void attachLiteralTypes(Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE));

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> Functions assigned in conditional blocks are inferred. for (Node current = n.getParent(); !(current.isScript() || current.isFunction()); current = current.getParent()) { if (NodeUtil.isControlStructure(current)) { return true; } } // Check if this is assigned in an inner scope. // Functions assigned in inner scopes are inferred. AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents == null || !contents.getEscapedQualifiedNames().contains(qName)) { return false; } } } return inferred; } private boolean isConstantSymbol(JSDocInfo info, Node node) { if (info != null && info.isConstant()) { return true; } switch (node.getType()) { case Token.NAME: return NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node, node.getParent()); case Token.GETPROP: return node.isQualifiedName() && NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node.getLastChild(), node); } return false; } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub declarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.isExtern; if (scope.isDeclared(qName, false)) { continue; } // If we see a stub property, make sure to register this property // in the type registry. ObjectType ownerType = getObjectSlot(ownerName); defineSlot(n, parent, unknownType, true); if (ownerType != null && (isExtern || ownerType.isFunctionPrototypeType())) { // If this is a stub for a prototype, just declare it // as an unknown type. These are seen often in externs. ownerType.defineInferredProperty( propName, unknownType, n); } else { typeRegistry.registerPropertyOnType( propName, ownerType == null ? unknownType : ownerType); } } } /** * Collects all declared properties in

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> a function, and * resolves them relative to the global scope. */ private final class CollectProperties extends AbstractShallowStatementCallback { private final JSType thisType; CollectProperties(JSType thisType) { this.thisType = thisType; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isExprResult()) { Node child = n.getFirstChild(); switch (child.getType()) { case Token.ASSIGN: maybeCollectMember(child.getFirstChild(), child, child.getLastChild()); break; case Token.GETPROP: maybeCollectMember(child, child, null); break; } } } private void maybeCollectMember(Node member, Node nodeWithJsDocInfo, @Nullable Node value) { JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo(); // Do nothing if there is no JSDoc type info, or // if the node is not a member expression, or // if the member expression is not of the form: this.someProperty. if (info == null || !member.isGetProp() || !member.getFirstChild().isThis()) { return; } member.getFirstChild().setJSType(thisType); // TODO(johnlenz): We are evaluating these values in the wrong scope: // https://code.google.com/p/closure-compiler/issues/detail?id=926 JSType thisObjectType = thisType.toObjectType(); if (thisObjectType != null) { ImmutableList<TemplateType> keys = thisObjectType.getTemplateTypeMap().getTemplateKeys(); typeRegistry.setTemplateTypeNames(keys); } JSType jsType = getDeclaredType(info, member, value); if (thisObjectType != null) { typeRegistry.clearTemplateTypeNames(); } Node name = member.getLastChild(); if (jsType != null && (name.isName() || name.isString()) && thisType.toObjectType() != null) { thisType.toObjectType().defineDeclaredProperty( name.getString(), jsType, member); } } } // end CollectProperties } /** * A stub declaration without any type information. */ private static final class StubDeclaration { private final Node node; private final boolean isExtern; private final String ownerName; private StubDeclaration(Node node, boolean isExtern, String ownerName) { this.node = node; this.isExtern = isExtern; this.ownerName = ownerName; } } /** * A shallow traversal of the global scope to build up all classes, * functions, and methods. */ private final class GlobalScopeBuilder extends AbstractScopeBuilder { private GlobalScopeBuilder(Scope scope) { super(scope); } /** * Visit a node in the global scope, and add anything it declares to the

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS> * global symbol table. * * @param t The current traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); switch (n.getType()) { case Token.VAR: // Handle typedefs. if (n.hasOneChild()) { checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; } } @Override void maybeDeclareQualifiedName( NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { checkForTypedef(t, n, info); super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue); } /** * Handle typedefs. * @param t The current traversal. * @param candidate A qualified name node. * @param info JSDoc comments. */ private void checkForTypedef( NodeTraversal t, Node candidate, JSDocInfo info) { if (info == null || !info.hasTypedefType()) { return; } String typedef = candidate.getQualifiedName(); if (typedef == null) { return; } // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recursive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, unknownType); JSType realType = info.getTypedefType().evaluate(scope, typeRegistry); if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); if (candidate.isGetProp()) { defineSlot(candidate, candidate.getParent(), getNativeType(NO_TYPE), false); } } } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're building. */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents != null) { for (String varName : contents.getEscapedVarNames()) { Var v = scope.getVar(varName); Preconditions.checkState(v.getScope() == scope); v.markEscaped(); } for (Multiset.Entry<String> entry : contents.getAssignedNameCounts().entrySet()) { Var v = scope.getVar(entry

Closure, 170

<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB> def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(final Node cfgNode) { numUsesWithinCfgNode = 0; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { <CHANGES> if (n.isName() && n.getString().equals(varName) && <CHANGEE> // We make a special exception when the entire cfgNode is a chain // of assignments, since in that case the assignment statements // will happen after the inlining of the right hand side. // TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact // and remove this special case. <CHANGES> !(parent.isAssign() && (parent.getFirstChild() == n))) { <CHANGEE> // Don't count lhs of top-level assignment chain <CHANGES> <CHANGEE> numUsesWithinCfgNode++; <CHANGES> <CHANGEE> } <CHANGES> <CHANGEE> } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Given an expression by its root and sub-expression n, return true if there * the predicate is true for some expression on the right of n. * * Example: * * NotChecked(), NotChecked(), n, Checked(), Checked(); <FILEE> <SCANS>.getElement()); Preconditions.checkState(v.getScope() == scope); if (entry.getCount() == 1) { v.markAssignedExactlyOnce(); } } } } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) { return; } if (n.isParamList() && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } super.visit(t, n, parent); } /** Handle bleeding functions and function parameters. */ private void handleFunctionInputs(Node fnNode) { // Handle bleeding functions. Node fnNameNode = fnNode.getFirstChild(); String fnName = fnNameNode.getString(); if (!fnName.isEmpty()) { Scope.Var fnVar = scope.getVar(fnName); if (fnVar == null || // Make sure we're not touching a native function. Native // functions aren't bleeding, but may not have a declaration // node. (fnVar.getNameNode() != null && // Make sure that the function is actually bleeding by checking // if has already been declared. fnVar.getInitialValue() != fnNode)) { defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false); } } declareArguments(fnNode); } /** * Declares all of a function's arguments. */ private void declareArguments(Node functionNode) { Node astParameters = functionNode.getFirstChild().getNext(); Node iifeArgumentNode = null; if (NodeUtil.isCallOrNewTarget(functionNode)) { iifeArgumentNode = functionNode.getNext(); } FunctionType functionType = JSType.toMaybeFunctionType(functionNode.getJSType()); if (functionType != null) { Node jsDocParameters = functionType.getParametersNode(); if (jsDocParameters != null) { Node jsDocParameter = jsDocParameters.getFirstChild(); for (Node astParameter : astParameters.children()) { JSType paramType = jsDocParameter == null ? unknownType : jsDocParameter.getJSType(); boolean inferred = paramType == null || paramType == unknownType; if (iifeArgumentNode != null && inferred) { String argumentName = iifeArgumentNode.getQualifiedName(); Var argumentVar = argumentName == null || scope.getParent() == null ? null : scope.getParent().getVar(argumentName); if (argumentVar != null && !argumentVar.isTypeInferred()) { paramType = argumentVar.getType(); } } if (paramType